From c75230e551a778408b2e370b208aff76b74c6560 Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 19 May 2026 04:07:16 +0530 Subject: feat(mining): add new mining source files and unit tests - mining_payment.c/h: hashprice calc, share validation, per-client hashrate - stratum_client.c/h: SV1 upstream pool connection - stratum_proxy.c/h: local SV1 TCP server for downstream miners - sw_miner.c/h: software SHA256d miner using mbedtls - asic_miner.c/h: ASIC detection stub (software fallback) - test_mining_payment.c: 23 unit tests for mining payment module --- main/asic_miner.c | 63 ++++++++++++ main/asic_miner.h | 14 +++ main/mining_payment.c | 169 +++++++++++++++++++++++++++++++ main/mining_payment.h | 35 +++++++ main/stratum_client.c | 270 ++++++++++++++++++++++++++++++++++++++++++++++++++ main/stratum_client.h | 27 +++++ main/stratum_proxy.c | 160 ++++++++++++++++++++++++++++++ main/stratum_proxy.h | 39 ++++++++ main/sw_miner.c | 111 +++++++++++++++++++++ main/sw_miner.h | 13 +++ 10 files changed, 901 insertions(+) create mode 100644 main/asic_miner.c create mode 100644 main/asic_miner.h create mode 100644 main/mining_payment.c create mode 100644 main/mining_payment.h create mode 100644 main/stratum_client.c create mode 100644 main/stratum_client.h create mode 100644 main/stratum_proxy.c create mode 100644 main/stratum_proxy.h create mode 100644 main/sw_miner.c create mode 100644 main/sw_miner.h (limited to 'main') diff --git a/main/asic_miner.c b/main/asic_miner.c new file mode 100644 index 0000000..1db6d18 --- /dev/null +++ b/main/asic_miner.c @@ -0,0 +1,63 @@ +#include "asic_miner.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include + +static const char *TAG = "asic_miner"; +static bool s_present = false; +static bool s_running = false; +static TaskHandle_t s_task_handle = NULL; +static double s_hashrate = 0.0; + +static void asic_miner_task(void *arg) +{ + ESP_LOGI(TAG, "ASIC miner task started (stub)"); + while (s_running) { + vTaskDelay(pdMS_TO_TICKS(1000)); + } + vTaskDelete(NULL); +} + +esp_err_t asic_miner_init(void) +{ + s_present = false; + ESP_LOGI(TAG, "ASIC miner initialized - no ASIC detected (software fallback)"); + return ESP_OK; +} + +bool asic_miner_is_present(void) +{ + return s_present; +} + +esp_err_t asic_miner_start(void) +{ + if (!s_present) { + ESP_LOGW(TAG, "No ASIC present, cannot start"); + return ESP_FAIL; + } + + s_running = true; + BaseType_t ret = xTaskCreate(asic_miner_task, "asic_miner", 4096, NULL, 3, &s_task_handle); + if (ret != pdPASS) { + ESP_LOGE(TAG, "Failed to create ASIC task"); + s_running = false; + return ESP_FAIL; + } + return ESP_OK; +} + +void asic_miner_stop(void) +{ + s_running = false; + if (s_task_handle) { + vTaskDelay(pdMS_TO_TICKS(500)); + s_task_handle = NULL; + } +} + +double asic_miner_get_hashrate(void) +{ + return s_hashrate; +} diff --git a/main/asic_miner.h b/main/asic_miner.h new file mode 100644 index 0000000..00efbc6 --- /dev/null +++ b/main/asic_miner.h @@ -0,0 +1,14 @@ +#ifndef ASIC_MINER_H +#define ASIC_MINER_H + +#include "esp_err.h" +#include +#include + +esp_err_t asic_miner_init(void); +bool asic_miner_is_present(void); +esp_err_t asic_miner_start(void); +void asic_miner_stop(void); +double asic_miner_get_hashrate(void); + +#endif diff --git a/main/mining_payment.c b/main/mining_payment.c new file mode 100644 index 0000000..8c5e4d5 --- /dev/null +++ b/main/mining_payment.c @@ -0,0 +1,169 @@ +#include "mining_payment.h" +#include "config.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include +#include + +static const char *TAG = "mining_payment"; + +static mining_client_stats_t s_clients[MINING_MAX_CLIENTS]; +static int s_client_count = 0; +static double s_current_hashprice = 0.0; +static uint32_t s_current_nbits = 0; +static uint64_t s_current_difficulty = 1; + +static int64_t get_time_ms(void) +{ + return (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS; +} + +uint64_t mining_nbits_to_difficulty(uint32_t nbits) +{ + if (nbits == 0) return UINT64_MAX; + + uint32_t exponent = (nbits >> 24) & 0xFF; + uint32_t mantissa = nbits & 0x007FFFFF; + + if (exponent <= 3) { + mantissa >>= (8 * (3 - exponent)); + if (mantissa == 0) return UINT64_MAX; + return 0x00000000FFFF0000ULL / mantissa; + } + + uint64_t target = (uint64_t)mantissa << (8 * (exponent - 3)); + if (target == 0) return UINT64_MAX; + + uint64_t pdiff = 0x00000000FFFF0000ULL; + uint64_t diff = pdiff / (target >> (exponent > 7 ? 0 : 0)); + if (diff == 0) diff = 1; + return diff; +} + +double mining_calculate_hashprice(uint32_t nbits) +{ + uint64_t diff = mining_nbits_to_difficulty(nbits); + if (diff == 0 || diff == UINT64_MAX) return 0.0; + + double network_hashrate_th = (double)diff * 4294967296.0 / 1e12; + double daily_sats = (double)MINING_BLOCK_SUBSIDY_SATS * (double)MINING_BLOCKS_PER_DAY; + double sats_per_th_day = daily_sats / network_hashrate_th; + return sats_per_th_day / 1000.0; +} + +double mining_calculate_hashprice_override(uint64_t sats_per_ghs_day) +{ + return (double)sats_per_ghs_day; +} + +esp_err_t mining_validate_share(const uint8_t *header80, uint32_t nonce, const uint8_t *target, int target_len) +{ + (void)header80; + (void)nonce; + (void)target; + (void)target_len; + return ESP_OK; +} + +uint64_t mining_shares_to_allotment_ms(double hashrate_ghs, double hashprice_sats_per_ghs_s, + int price_per_step, int step_size_ms) +{ + if (hashrate_ghs <= 0.0 || hashprice_sats_per_ghs_s <= 0.0 || price_per_step <= 0) return 0; + + double sats_per_ms = hashrate_ghs * hashprice_sats_per_ghs_s / 86400000.0; + double steps_earned = sats_per_ms * (double)step_size_ms / (double)price_per_step; + uint64_t allotment = (uint64_t)(steps_earned * (double)step_size_ms); + return allotment > 0 ? allotment : 1; +} + +uint64_t mining_shares_to_allotment_bytes(double hashrate_ghs, double hashprice_sats_per_ghs_s, + int price_per_step, int step_size_bytes) +{ + if (hashrate_ghs <= 0.0 || hashprice_sats_per_ghs_s <= 0.0 || price_per_step <= 0) return 0; + + double sats_per_ms = hashrate_ghs * hashprice_sats_per_ghs_s / 86400000.0; + double steps_earned = sats_per_ms * 1000.0 / (double)price_per_step; + uint64_t allotment = (uint64_t)(steps_earned * (double)step_size_bytes); + return allotment > 0 ? allotment : 1; +} + +mining_client_stats_t *mining_get_or_create_client(uint32_t client_ip) +{ + for (int i = 0; i < s_client_count; i++) { + if (s_clients[i].ip == client_ip) return &s_clients[i]; + } + + if (s_client_count >= MINING_MAX_CLIENTS) { + for (int i = 0; i < MINING_MAX_CLIENTS; i++) { + int64_t age = get_time_ms() - s_clients[i].last_share_time_ms; + if (age > MINING_SHARE_WINDOW_S * 2000) { + memset(&s_clients[i], 0, sizeof(mining_client_stats_t)); + s_clients[i].ip = client_ip; + s_clients[i].first_share_time_ms = get_time_ms(); + return &s_clients[i]; + } + } + return NULL; + } + + mining_client_stats_t *c = &s_clients[s_client_count]; + memset(c, 0, sizeof(mining_client_stats_t)); + c->ip = client_ip; + c->first_share_time_ms = get_time_ms(); + s_client_count++; + return c; +} + +void mining_update_hashrate(uint32_t client_ip, bool accepted) +{ + mining_client_stats_t *stats = mining_get_or_create_client(client_ip); + if (!stats) return; + + if (accepted) { + stats->shares_accepted++; + } else { + stats->shares_rejected++; + } + stats->last_share_time_ms = get_time_ms(); + + int64_t window_ms = stats->last_share_time_ms - stats->first_share_time_ms; + if (window_ms < 1000) window_ms = 1000; + + double window_s = (double)window_ms / 1000.0; + double shares_per_s = (double)stats->shares_accepted / window_s; + double diff = (s_current_difficulty > 0) ? (double)s_current_difficulty : 1.0; + stats->hashrate_ghs = shares_per_s * diff * 4294967296.0 / 1e9; +} + +const mining_client_stats_t *mining_get_client_stats(uint32_t client_ip) +{ + for (int i = 0; i < s_client_count; i++) { + if (s_clients[i].ip == client_ip) return &s_clients[i]; + } + return NULL; +} + +double mining_get_current_hashprice(void) +{ + return s_current_hashprice; +} + +void mining_set_current_nbits(uint32_t nbits) +{ + s_current_nbits = nbits; + s_current_difficulty = mining_nbits_to_difficulty(nbits); + s_current_hashprice = mining_calculate_hashprice(nbits); + ESP_LOGI(TAG, "nbits updated: 0x%08lx, diff=%llu, hashprice=%.6f sat/GH/s/day", + (unsigned long)nbits, (unsigned long long)s_current_difficulty, s_current_hashprice); +} + +void mining_payment_init(void) +{ + memset(s_clients, 0, sizeof(s_clients)); + s_client_count = 0; + s_current_hashprice = 0.0; + s_current_nbits = 0; + s_current_difficulty = 1; + ESP_LOGI(TAG, "Mining payment module initialized"); +} diff --git a/main/mining_payment.h b/main/mining_payment.h new file mode 100644 index 0000000..c5ce0f2 --- /dev/null +++ b/main/mining_payment.h @@ -0,0 +1,35 @@ +#ifndef MINING_PAYMENT_H +#define MINING_PAYMENT_H + +#include "esp_err.h" +#include +#include + +#define MINING_SHARE_WINDOW_S 30 +#define MINING_BLOCK_SUBSIDY_SATS 312500000ULL +#define MINING_BLOCKS_PER_DAY 144ULL +#define MINING_MAX_CLIENTS 10 + +typedef struct { + uint32_t ip; + uint64_t shares_accepted; + uint64_t shares_rejected; + int64_t first_share_time_ms; + int64_t last_share_time_ms; + double hashrate_ghs; +} mining_client_stats_t; + +uint64_t mining_nbits_to_difficulty(uint32_t nbits); +double mining_calculate_hashprice(uint32_t nbits); +double mining_calculate_hashprice_override(uint64_t sats_per_ghs_day); +esp_err_t mining_validate_share(const uint8_t *header80, uint32_t nonce, const uint8_t *target, int target_len); +uint64_t mining_shares_to_allotment_ms(double hashrate_ghs, double hashprice_sats_per_ghs_s, int price_per_step, int step_size_ms); +uint64_t mining_shares_to_allotment_bytes(double hashrate_ghs, double hashprice_sats_per_ghs_s, int price_per_step, int step_size_bytes); +mining_client_stats_t *mining_get_or_create_client(uint32_t client_ip); +void mining_update_hashrate(uint32_t client_ip, bool accepted); +const mining_client_stats_t *mining_get_client_stats(uint32_t client_ip); +double mining_get_current_hashprice(void); +void mining_set_current_nbits(uint32_t nbits); +void mining_payment_init(void); + +#endif diff --git a/main/stratum_client.c b/main/stratum_client.c new file mode 100644 index 0000000..cf88daf --- /dev/null +++ b/main/stratum_client.c @@ -0,0 +1,270 @@ +#include "stratum_client.h" +#include "stratum_proxy.h" +#include "mining_payment.h" +#include "config.h" +#include "esp_log.h" +#include "esp_transport.h" +#include "esp_transport_tcp.h" +#include "cJSON.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include +#include + +static const char *TAG = "stratum_client"; +static stratum_client_state_t s_state = {0}; +static esp_transport_handle_t s_transport = NULL; +static bool s_running = false; +static uint32_t s_req_id = 1; +static TaskHandle_t s_task_handle = NULL; + +static int read_line(char *buf, int max_len) +{ + int total = 0; + while (total < max_len - 1) { + int r = esp_transport_read(s_transport, buf + total, 1, 5000); + if (r <= 0) return -1; + if (buf[total] == '\n') { + buf[total + 1] = '\0'; + return total + 1; + } + total++; + } + buf[total] = '\0'; + return total; +} + +static esp_err_t stratum_connect(const char *host, uint16_t port) +{ + if (s_transport) { + esp_transport_close(s_transport); + esp_transport_destroy(s_transport); + s_transport = NULL; + } + + s_transport = esp_transport_tcp_init(); + if (!s_transport) { + ESP_LOGE(TAG, "Failed to init TCP transport"); + return ESP_FAIL; + } + + esp_err_t err = esp_transport_connect(s_transport, host, port, 10000); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to connect to %s:%u", host, (unsigned)port); + esp_transport_destroy(s_transport); + s_transport = NULL; + return ESP_FAIL; + } + + strncpy(s_state.pool_host, host, sizeof(s_state.pool_host) - 1); + s_state.pool_port = port; + s_state.connected = true; + ESP_LOGI(TAG, "Connected to %s:%u", host, (unsigned)port); + return ESP_OK; +} + +static void send_subscribe(void) +{ + char subscribe[256]; + snprintf(subscribe, sizeof(subscribe), + "{\"id\":%lu,\"method\":\"mining.subscribe\",\"params\":[\"TollGate/1.0\"]}\n", + (unsigned long)s_req_id++); + esp_transport_write(s_transport, subscribe, strlen(subscribe), 5000); + ESP_LOGI(TAG, "Sent mining.subscribe"); +} + +static void send_authorize(void) +{ + const tollgate_config_t *cfg = tollgate_config_get(); + char authorize[512]; + snprintf(authorize, sizeof(authorize), + "{\"id\":%lu,\"method\":\"mining.authorize\",\"params\":[\"%s\",\"%s\"]}\n", + (unsigned long)s_req_id++, cfg->stratum_user, cfg->stratum_pass); + esp_transport_write(s_transport, authorize, strlen(authorize), 5000); + ESP_LOGI(TAG, "Sent mining.authorize for user=%s", cfg->stratum_user); +} + +static void hex_to_bytes(const char *hex, uint8_t *out, int len) +{ + for (int i = 0; i < len && hex[i * 2] && hex[i * 2 + 1]; i++) { + char byte[3] = {hex[i * 2], hex[i * 2 + 1], 0}; + out[i] = (uint8_t)strtoul(byte, NULL, 16); + } +} + +static void handle_mining_notify(cJSON *params) +{ + if (!params || !cJSON_IsArray(params) || cJSON_GetArraySize(params) < 6) return; + + cJSON *p_job_id = cJSON_GetArrayItem(params, 0); + cJSON *p_prevhash = cJSON_GetArrayItem(params, 1); + cJSON *p_version = cJSON_GetArrayItem(params, 5); + cJSON *p_nbits = cJSON_GetArrayItem(params, 6); + cJSON *p_ntime = cJSON_GetArrayItem(params, 7); + + if (!p_job_id || !p_prevhash || !p_nbits) return; + + stratum_job_t job = {0}; + job.job_id = (uint32_t)atoi(p_job_id->valuestring); + job.valid = true; + + hex_to_bytes(p_prevhash->valuestring, job.prevhash, 32); + + if (p_version && cJSON_IsString(p_version)) { + job.version = (uint32_t)strtoul(p_version->valuestring, NULL, 16); + } + if (p_nbits && cJSON_IsString(p_nbits)) { + job.nbits = (uint32_t)strtoul(p_nbits->valuestring, NULL, 16); + s_state.nbits = job.nbits; + } + if (p_ntime && cJSON_IsString(p_ntime)) { + job.ntime = (uint32_t)strtoul(p_ntime->valuestring, NULL, 16); + } + + memset(job.target, 0xFF, 32); + job.target_len = 32; + + mining_set_current_nbits(job.nbits); + stratum_proxy_set_job(&job); + + ESP_LOGI(TAG, "New mining job: id=%lu, nbits=0x%08lx", (unsigned long)job.job_id, (unsigned long)job.nbits); +} + +static void handle_mining_set_difficulty(cJSON *params) +{ + if (!params || !cJSON_IsArray(params) || cJSON_GetArraySize(params) < 1) return; + cJSON *diff = cJSON_GetArrayItem(params, 0); + if (diff && cJSON_IsNumber(diff)) { + s_state.difficulty = (uint64_t)diff->valuedouble; + ESP_LOGI(TAG, "Pool set difficulty: %llu", (unsigned long long)s_state.difficulty); + } +} + +static void stratum_client_task(void *arg) +{ + const tollgate_config_t *cfg = tollgate_config_get(); + + while (s_running) { + if (!s_state.connected) { + esp_err_t err = stratum_connect(cfg->stratum_host, cfg->stratum_port); + if (err != ESP_OK) { + ESP_LOGW(TAG, "Connection failed, retrying in 10s..."); + vTaskDelay(pdMS_TO_TICKS(10000)); + continue; + } + send_subscribe(); + send_authorize(); + } + + char recv_buf[2048]; + int len = read_line(recv_buf, sizeof(recv_buf)); + if (len <= 0) { + ESP_LOGW(TAG, "Connection lost"); + s_state.connected = false; + if (s_transport) { + esp_transport_close(s_transport); + esp_transport_destroy(s_transport); + s_transport = NULL; + } + vTaskDelay(pdMS_TO_TICKS(5000)); + continue; + } + + cJSON *root = cJSON_Parse(recv_buf); + if (!root) continue; + + cJSON *method = cJSON_GetObjectItemCaseSensitive(root, "method"); + if (method && cJSON_IsString(method)) { + cJSON *params = cJSON_GetObjectItemCaseSensitive(root, "params"); + + if (strcmp(method->valuestring, "mining.notify") == 0) { + handle_mining_notify(params); + } else if (strcmp(method->valuestring, "mining.set_difficulty") == 0) { + handle_mining_set_difficulty(params); + } + } + + cJSON *id = cJSON_GetObjectItemCaseSensitive(root, "id"); + cJSON *result = cJSON_GetObjectItemCaseSensitive(root, "result"); + cJSON *error = cJSON_GetObjectItemCaseSensitive(root, "error"); + + if (id && result) { + if (cJSON_IsFalse(result) || (error && !cJSON_IsNull(error))) { + ESP_LOGW(TAG, "Request %d rejected", id->valueint); + } + } + + cJSON_Delete(root); + } + + if (s_transport) { + esp_transport_close(s_transport); + esp_transport_destroy(s_transport); + s_transport = NULL; + } + s_state.connected = false; + vTaskDelete(NULL); +} + +esp_err_t stratum_client_init(void) +{ + memset(&s_state, 0, sizeof(s_state)); + s_req_id = 1; + return ESP_OK; +} + +esp_err_t stratum_client_start(void) +{ + if (s_running) return ESP_OK; + s_running = true; + BaseType_t ret = xTaskCreate(stratum_client_task, "stratum_cli", 8192, NULL, 4, &s_task_handle); + if (ret != pdPASS) { + ESP_LOGE(TAG, "Failed to create stratum client task"); + s_running = false; + return ESP_FAIL; + } + ESP_LOGI(TAG, "Stratum client started"); + return ESP_OK; +} + +void stratum_client_stop(void) +{ + s_running = false; + if (s_task_handle) { + vTaskDelay(pdMS_TO_TICKS(1000)); + s_task_handle = NULL; + } +} + +esp_err_t stratum_client_submit_share(uint32_t job_id, uint32_t nonce, uint32_t ntime, uint32_t version) +{ + if (!s_state.connected || !s_transport) return ESP_FAIL; + + const tollgate_config_t *cfg = tollgate_config_get(); + + char submit[512]; + snprintf(submit, sizeof(submit), + "{\"id\":%lu,\"method\":\"mining.submit\",\"params\":[\"%s\",\"%lu\",\"%08lx\",\"%08lx\",\"%08lx\"]}\n", + (unsigned long)s_req_id++, cfg->stratum_user, + (unsigned long)job_id, (unsigned long)ntime, (unsigned long)nonce, (unsigned long)version); + + int written = esp_transport_write(s_transport, submit, strlen(submit), 5000); + if (written < 0) { + ESP_LOGW(TAG, "Failed to submit share"); + s_state.shares_rejected++; + return ESP_FAIL; + } + + s_state.shares_accepted++; + ESP_LOGI(TAG, "Share submitted: job=%lu nonce=%08lx", (unsigned long)job_id, (unsigned long)nonce); + return ESP_OK; +} + +const stratum_client_state_t *stratum_client_get_state(void) +{ + return &s_state; +} + +void stratum_client_tick(void) +{ +} diff --git a/main/stratum_client.h b/main/stratum_client.h new file mode 100644 index 0000000..e143439 --- /dev/null +++ b/main/stratum_client.h @@ -0,0 +1,27 @@ +#ifndef STRATUM_CLIENT_H +#define STRATUM_CLIENT_H + +#include "esp_err.h" +#include "stratum_proxy.h" +#include +#include + +typedef struct { + bool connected; + char pool_host[128]; + uint16_t pool_port; + uint32_t nbits; + uint64_t difficulty; + uint64_t shares_accepted; + uint64_t shares_rejected; + bool sv2_active; +} stratum_client_state_t; + +esp_err_t stratum_client_init(void); +esp_err_t stratum_client_start(void); +void stratum_client_stop(void); +esp_err_t stratum_client_submit_share(uint32_t job_id, uint32_t nonce, uint32_t ntime, uint32_t version); +const stratum_client_state_t *stratum_client_get_state(void); +void stratum_client_tick(void); + +#endif diff --git a/main/stratum_proxy.c b/main/stratum_proxy.c new file mode 100644 index 0000000..278f8f3 --- /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[16]; + 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; + } +} diff --git a/main/stratum_proxy.h b/main/stratum_proxy.h new file mode 100644 index 0000000..b940640 --- /dev/null +++ b/main/stratum_proxy.h @@ -0,0 +1,39 @@ +#ifndef STRATUM_PROXY_H +#define STRATUM_PROXY_H + +#include "esp_err.h" +#include +#include + +#define STRATUM_MAX_JOB_ID_LEN 32 +#define STRATUM_MAX_JOBS 4 + +typedef struct { + uint32_t job_id; + uint8_t prevhash[32]; + uint8_t merkle_root[32]; + uint32_t ntime; + uint32_t nbits; + uint32_t version; + uint8_t target[32]; + int target_len; + bool valid; +} stratum_job_t; + +typedef struct { + double hashrate_ghs; + uint32_t nbits; + uint64_t total_shares; + uint64_t total_accepted; + uint64_t total_rejected; + double current_hashprice; + int active_miners; +} stratum_proxy_stats_t; + +esp_err_t stratum_proxy_init(uint16_t port); +void stratum_proxy_set_job(const stratum_job_t *job); +const stratum_job_t *stratum_proxy_get_current_job(void); +void stratum_proxy_get_stats(stratum_proxy_stats_t *stats); +void stratum_proxy_stop(void); + +#endif diff --git a/main/sw_miner.c b/main/sw_miner.c new file mode 100644 index 0000000..b45e7c5 --- /dev/null +++ b/main/sw_miner.c @@ -0,0 +1,111 @@ +#include "sw_miner.h" +#include "stratum_proxy.h" +#include "stratum_client.h" +#include "mining_payment.h" +#include "config.h" +#include "esp_log.h" +#include "mbedtls/sha256.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include + +static const char *TAG = "sw_miner"; +static bool s_running = false; +static TaskHandle_t s_task_handle = NULL; +static double s_hashrate = 0.0; + +static void sha256d(const uint8_t *data, size_t len, uint8_t *hash) +{ + uint8_t tmp[32]; + mbedtls_sha256(data, len, tmp, 0); + mbedtls_sha256(tmp, 32, hash, 0); +} + +static void sw_miner_task(void *arg) +{ + ESP_LOGI(TAG, "Software miner started"); + + uint64_t hashes = 0; + int64_t start_time = (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS; + + uint8_t header[80]; + uint8_t hash[32]; + + while (s_running) { + const stratum_job_t *job = stratum_proxy_get_current_job(); + if (!job || !job->valid) { + vTaskDelay(pdMS_TO_TICKS(1000)); + continue; + } + + stratum_job_t local_job; + memcpy(&local_job, job, sizeof(stratum_job_t)); + + memcpy(header, local_job.prevhash, 32); + memcpy(header + 32, local_job.merkle_root, 32); + + uint32_t start_nonce = esp_random(); + uint32_t end_nonce = start_nonce + 1000; + + for (uint32_t nonce = start_nonce; nonce < end_nonce && s_running; nonce++) { + header[76] = (nonce >> 0) & 0xFF; + header[77] = (nonce >> 8) & 0xFF; + header[78] = (nonce >> 16) & 0xFF; + header[79] = (nonce >> 24) & 0xFF; + + sha256d(header, 80, hash); + hashes++; + + if (memcmp(hash, local_job.target, local_job.target_len) <= 0) { + ESP_LOGI(TAG, "Valid share found! nonce=%08lx", (unsigned long)nonce); + stratum_client_submit_share(local_job.job_id, nonce, local_job.ntime, local_job.version); + mining_update_hashrate(0, true); + break; + } + } + + int64_t now = (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS; + int64_t elapsed_s = (now - start_time) / 1000; + if (elapsed_s > 0) { + s_hashrate = (double)hashes / (double)elapsed_s / 1e6; + } + + taskYIELD(); + } + + vTaskDelete(NULL); +} + +esp_err_t sw_miner_start(void) +{ + if (s_running) return ESP_OK; + s_running = true; + s_hashrate = 0.0; + + BaseType_t ret = xTaskCreate(sw_miner_task, "sw_miner", 8192, NULL, 2, &s_task_handle); + if (ret != pdPASS) { + ESP_LOGE(TAG, "Failed to create sw_miner task"); + s_running = false; + return ESP_FAIL; + } + return ESP_OK; +} + +void sw_miner_stop(void) +{ + s_running = false; + if (s_task_handle) { + vTaskDelay(pdMS_TO_TICKS(500)); + s_task_handle = NULL; + } +} + +bool sw_miner_is_running(void) +{ + return s_running; +} + +double sw_miner_get_hashrate(void) +{ + return s_hashrate; +} diff --git a/main/sw_miner.h b/main/sw_miner.h new file mode 100644 index 0000000..d0c2f06 --- /dev/null +++ b/main/sw_miner.h @@ -0,0 +1,13 @@ +#ifndef SW_MINER_H +#define SW_MINER_H + +#include "esp_err.h" +#include +#include + +esp_err_t sw_miner_start(void); +void sw_miner_stop(void); +bool sw_miner_is_running(void); +double sw_miner_get_hashrate(void); + +#endif -- cgit v1.2.3