diff options
Diffstat (limited to 'main')
| -rw-r--r-- | main/asic_miner.c | 63 | ||||
| -rw-r--r-- | main/asic_miner.h | 14 | ||||
| -rw-r--r-- | main/mining_payment.c | 169 | ||||
| -rw-r--r-- | main/mining_payment.h | 35 | ||||
| -rw-r--r-- | main/stratum_client.c | 270 | ||||
| -rw-r--r-- | main/stratum_client.h | 27 | ||||
| -rw-r--r-- | main/stratum_proxy.c | 160 | ||||
| -rw-r--r-- | main/stratum_proxy.h | 39 | ||||
| -rw-r--r-- | main/sw_miner.c | 111 | ||||
| -rw-r--r-- | main/sw_miner.h | 13 |
10 files changed, 901 insertions, 0 deletions
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 @@ | |||
| 1 | #include "asic_miner.h" | ||
| 2 | #include "esp_log.h" | ||
| 3 | #include "freertos/FreeRTOS.h" | ||
| 4 | #include "freertos/task.h" | ||
| 5 | #include <string.h> | ||
| 6 | |||
| 7 | static const char *TAG = "asic_miner"; | ||
| 8 | static bool s_present = false; | ||
| 9 | static bool s_running = false; | ||
| 10 | static TaskHandle_t s_task_handle = NULL; | ||
| 11 | static double s_hashrate = 0.0; | ||
| 12 | |||
| 13 | static void asic_miner_task(void *arg) | ||
| 14 | { | ||
| 15 | ESP_LOGI(TAG, "ASIC miner task started (stub)"); | ||
| 16 | while (s_running) { | ||
| 17 | vTaskDelay(pdMS_TO_TICKS(1000)); | ||
| 18 | } | ||
| 19 | vTaskDelete(NULL); | ||
| 20 | } | ||
| 21 | |||
| 22 | esp_err_t asic_miner_init(void) | ||
| 23 | { | ||
| 24 | s_present = false; | ||
| 25 | ESP_LOGI(TAG, "ASIC miner initialized - no ASIC detected (software fallback)"); | ||
| 26 | return ESP_OK; | ||
| 27 | } | ||
| 28 | |||
| 29 | bool asic_miner_is_present(void) | ||
| 30 | { | ||
| 31 | return s_present; | ||
| 32 | } | ||
| 33 | |||
| 34 | esp_err_t asic_miner_start(void) | ||
| 35 | { | ||
| 36 | if (!s_present) { | ||
| 37 | ESP_LOGW(TAG, "No ASIC present, cannot start"); | ||
| 38 | return ESP_FAIL; | ||
| 39 | } | ||
| 40 | |||
| 41 | s_running = true; | ||
| 42 | BaseType_t ret = xTaskCreate(asic_miner_task, "asic_miner", 4096, NULL, 3, &s_task_handle); | ||
| 43 | if (ret != pdPASS) { | ||
| 44 | ESP_LOGE(TAG, "Failed to create ASIC task"); | ||
| 45 | s_running = false; | ||
| 46 | return ESP_FAIL; | ||
| 47 | } | ||
| 48 | return ESP_OK; | ||
| 49 | } | ||
| 50 | |||
| 51 | void asic_miner_stop(void) | ||
| 52 | { | ||
| 53 | s_running = false; | ||
| 54 | if (s_task_handle) { | ||
| 55 | vTaskDelay(pdMS_TO_TICKS(500)); | ||
| 56 | s_task_handle = NULL; | ||
| 57 | } | ||
| 58 | } | ||
| 59 | |||
| 60 | double asic_miner_get_hashrate(void) | ||
| 61 | { | ||
| 62 | return s_hashrate; | ||
| 63 | } | ||
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 @@ | |||
| 1 | #ifndef ASIC_MINER_H | ||
| 2 | #define ASIC_MINER_H | ||
| 3 | |||
| 4 | #include "esp_err.h" | ||
| 5 | #include <stdint.h> | ||
| 6 | #include <stdbool.h> | ||
| 7 | |||
| 8 | esp_err_t asic_miner_init(void); | ||
| 9 | bool asic_miner_is_present(void); | ||
| 10 | esp_err_t asic_miner_start(void); | ||
| 11 | void asic_miner_stop(void); | ||
| 12 | double asic_miner_get_hashrate(void); | ||
| 13 | |||
| 14 | #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 @@ | |||
| 1 | #include "mining_payment.h" | ||
| 2 | #include "config.h" | ||
| 3 | #include "esp_log.h" | ||
| 4 | #include "freertos/FreeRTOS.h" | ||
| 5 | #include "freertos/task.h" | ||
| 6 | #include <string.h> | ||
| 7 | #include <math.h> | ||
| 8 | |||
| 9 | static const char *TAG = "mining_payment"; | ||
| 10 | |||
| 11 | static mining_client_stats_t s_clients[MINING_MAX_CLIENTS]; | ||
| 12 | static int s_client_count = 0; | ||
| 13 | static double s_current_hashprice = 0.0; | ||
| 14 | static uint32_t s_current_nbits = 0; | ||
| 15 | static uint64_t s_current_difficulty = 1; | ||
| 16 | |||
| 17 | static int64_t get_time_ms(void) | ||
| 18 | { | ||
| 19 | return (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS; | ||
| 20 | } | ||
| 21 | |||
| 22 | uint64_t mining_nbits_to_difficulty(uint32_t nbits) | ||
| 23 | { | ||
| 24 | if (nbits == 0) return UINT64_MAX; | ||
| 25 | |||
| 26 | uint32_t exponent = (nbits >> 24) & 0xFF; | ||
| 27 | uint32_t mantissa = nbits & 0x007FFFFF; | ||
| 28 | |||
| 29 | if (exponent <= 3) { | ||
| 30 | mantissa >>= (8 * (3 - exponent)); | ||
| 31 | if (mantissa == 0) return UINT64_MAX; | ||
| 32 | return 0x00000000FFFF0000ULL / mantissa; | ||
| 33 | } | ||
| 34 | |||
| 35 | uint64_t target = (uint64_t)mantissa << (8 * (exponent - 3)); | ||
| 36 | if (target == 0) return UINT64_MAX; | ||
| 37 | |||
| 38 | uint64_t pdiff = 0x00000000FFFF0000ULL; | ||
| 39 | uint64_t diff = pdiff / (target >> (exponent > 7 ? 0 : 0)); | ||
| 40 | if (diff == 0) diff = 1; | ||
| 41 | return diff; | ||
| 42 | } | ||
| 43 | |||
| 44 | double mining_calculate_hashprice(uint32_t nbits) | ||
| 45 | { | ||
| 46 | uint64_t diff = mining_nbits_to_difficulty(nbits); | ||
| 47 | if (diff == 0 || diff == UINT64_MAX) return 0.0; | ||
| 48 | |||
| 49 | double network_hashrate_th = (double)diff * 4294967296.0 / 1e12; | ||
| 50 | double daily_sats = (double)MINING_BLOCK_SUBSIDY_SATS * (double)MINING_BLOCKS_PER_DAY; | ||
| 51 | double sats_per_th_day = daily_sats / network_hashrate_th; | ||
| 52 | return sats_per_th_day / 1000.0; | ||
| 53 | } | ||
| 54 | |||
| 55 | double mining_calculate_hashprice_override(uint64_t sats_per_ghs_day) | ||
| 56 | { | ||
| 57 | return (double)sats_per_ghs_day; | ||
| 58 | } | ||
| 59 | |||
| 60 | esp_err_t mining_validate_share(const uint8_t *header80, uint32_t nonce, const uint8_t *target, int target_len) | ||
| 61 | { | ||
| 62 | (void)header80; | ||
| 63 | (void)nonce; | ||
| 64 | (void)target; | ||
| 65 | (void)target_len; | ||
| 66 | return ESP_OK; | ||
| 67 | } | ||
| 68 | |||
| 69 | uint64_t mining_shares_to_allotment_ms(double hashrate_ghs, double hashprice_sats_per_ghs_s, | ||
| 70 | int price_per_step, int step_size_ms) | ||
| 71 | { | ||
| 72 | if (hashrate_ghs <= 0.0 || hashprice_sats_per_ghs_s <= 0.0 || price_per_step <= 0) return 0; | ||
| 73 | |||
| 74 | double sats_per_ms = hashrate_ghs * hashprice_sats_per_ghs_s / 86400000.0; | ||
| 75 | double steps_earned = sats_per_ms * (double)step_size_ms / (double)price_per_step; | ||
| 76 | uint64_t allotment = (uint64_t)(steps_earned * (double)step_size_ms); | ||
| 77 | return allotment > 0 ? allotment : 1; | ||
| 78 | } | ||
| 79 | |||
| 80 | uint64_t mining_shares_to_allotment_bytes(double hashrate_ghs, double hashprice_sats_per_ghs_s, | ||
| 81 | int price_per_step, int step_size_bytes) | ||
| 82 | { | ||
| 83 | if (hashrate_ghs <= 0.0 || hashprice_sats_per_ghs_s <= 0.0 || price_per_step <= 0) return 0; | ||
| 84 | |||
| 85 | double sats_per_ms = hashrate_ghs * hashprice_sats_per_ghs_s / 86400000.0; | ||
| 86 | double steps_earned = sats_per_ms * 1000.0 / (double)price_per_step; | ||
| 87 | uint64_t allotment = (uint64_t)(steps_earned * (double)step_size_bytes); | ||
| 88 | return allotment > 0 ? allotment : 1; | ||
| 89 | } | ||
| 90 | |||
| 91 | mining_client_stats_t *mining_get_or_create_client(uint32_t client_ip) | ||
| 92 | { | ||
| 93 | for (int i = 0; i < s_client_count; i++) { | ||
| 94 | if (s_clients[i].ip == client_ip) return &s_clients[i]; | ||
| 95 | } | ||
| 96 | |||
| 97 | if (s_client_count >= MINING_MAX_CLIENTS) { | ||
| 98 | for (int i = 0; i < MINING_MAX_CLIENTS; i++) { | ||
| 99 | int64_t age = get_time_ms() - s_clients[i].last_share_time_ms; | ||
| 100 | if (age > MINING_SHARE_WINDOW_S * 2000) { | ||
| 101 | memset(&s_clients[i], 0, sizeof(mining_client_stats_t)); | ||
| 102 | s_clients[i].ip = client_ip; | ||
| 103 | s_clients[i].first_share_time_ms = get_time_ms(); | ||
| 104 | return &s_clients[i]; | ||
| 105 | } | ||
| 106 | } | ||
| 107 | return NULL; | ||
| 108 | } | ||
| 109 | |||
| 110 | mining_client_stats_t *c = &s_clients[s_client_count]; | ||
| 111 | memset(c, 0, sizeof(mining_client_stats_t)); | ||
| 112 | c->ip = client_ip; | ||
| 113 | c->first_share_time_ms = get_time_ms(); | ||
| 114 | s_client_count++; | ||
| 115 | return c; | ||
| 116 | } | ||
| 117 | |||
| 118 | void mining_update_hashrate(uint32_t client_ip, bool accepted) | ||
| 119 | { | ||
| 120 | mining_client_stats_t *stats = mining_get_or_create_client(client_ip); | ||
| 121 | if (!stats) return; | ||
| 122 | |||
| 123 | if (accepted) { | ||
| 124 | stats->shares_accepted++; | ||
| 125 | } else { | ||
| 126 | stats->shares_rejected++; | ||
| 127 | } | ||
| 128 | stats->last_share_time_ms = get_time_ms(); | ||
| 129 | |||
| 130 | int64_t window_ms = stats->last_share_time_ms - stats->first_share_time_ms; | ||
| 131 | if (window_ms < 1000) window_ms = 1000; | ||
| 132 | |||
| 133 | double window_s = (double)window_ms / 1000.0; | ||
| 134 | double shares_per_s = (double)stats->shares_accepted / window_s; | ||
| 135 | double diff = (s_current_difficulty > 0) ? (double)s_current_difficulty : 1.0; | ||
| 136 | stats->hashrate_ghs = shares_per_s * diff * 4294967296.0 / 1e9; | ||
| 137 | } | ||
| 138 | |||
| 139 | const mining_client_stats_t *mining_get_client_stats(uint32_t client_ip) | ||
| 140 | { | ||
| 141 | for (int i = 0; i < s_client_count; i++) { | ||
| 142 | if (s_clients[i].ip == client_ip) return &s_clients[i]; | ||
| 143 | } | ||
| 144 | return NULL; | ||
| 145 | } | ||
| 146 | |||
| 147 | double mining_get_current_hashprice(void) | ||
| 148 | { | ||
| 149 | return s_current_hashprice; | ||
| 150 | } | ||
| 151 | |||
| 152 | void mining_set_current_nbits(uint32_t nbits) | ||
| 153 | { | ||
| 154 | s_current_nbits = nbits; | ||
| 155 | s_current_difficulty = mining_nbits_to_difficulty(nbits); | ||
| 156 | s_current_hashprice = mining_calculate_hashprice(nbits); | ||
| 157 | ESP_LOGI(TAG, "nbits updated: 0x%08lx, diff=%llu, hashprice=%.6f sat/GH/s/day", | ||
| 158 | (unsigned long)nbits, (unsigned long long)s_current_difficulty, s_current_hashprice); | ||
| 159 | } | ||
| 160 | |||
| 161 | void mining_payment_init(void) | ||
| 162 | { | ||
| 163 | memset(s_clients, 0, sizeof(s_clients)); | ||
| 164 | s_client_count = 0; | ||
| 165 | s_current_hashprice = 0.0; | ||
| 166 | s_current_nbits = 0; | ||
| 167 | s_current_difficulty = 1; | ||
| 168 | ESP_LOGI(TAG, "Mining payment module initialized"); | ||
| 169 | } | ||
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 @@ | |||
| 1 | #ifndef MINING_PAYMENT_H | ||
| 2 | #define MINING_PAYMENT_H | ||
| 3 | |||
| 4 | #include "esp_err.h" | ||
| 5 | #include <stdint.h> | ||
| 6 | #include <stdbool.h> | ||
| 7 | |||
| 8 | #define MINING_SHARE_WINDOW_S 30 | ||
| 9 | #define MINING_BLOCK_SUBSIDY_SATS 312500000ULL | ||
| 10 | #define MINING_BLOCKS_PER_DAY 144ULL | ||
| 11 | #define MINING_MAX_CLIENTS 10 | ||
| 12 | |||
| 13 | typedef struct { | ||
| 14 | uint32_t ip; | ||
| 15 | uint64_t shares_accepted; | ||
| 16 | uint64_t shares_rejected; | ||
| 17 | int64_t first_share_time_ms; | ||
| 18 | int64_t last_share_time_ms; | ||
| 19 | double hashrate_ghs; | ||
| 20 | } mining_client_stats_t; | ||
| 21 | |||
| 22 | uint64_t mining_nbits_to_difficulty(uint32_t nbits); | ||
| 23 | double mining_calculate_hashprice(uint32_t nbits); | ||
| 24 | double mining_calculate_hashprice_override(uint64_t sats_per_ghs_day); | ||
| 25 | esp_err_t mining_validate_share(const uint8_t *header80, uint32_t nonce, const uint8_t *target, int target_len); | ||
| 26 | uint64_t mining_shares_to_allotment_ms(double hashrate_ghs, double hashprice_sats_per_ghs_s, int price_per_step, int step_size_ms); | ||
| 27 | uint64_t mining_shares_to_allotment_bytes(double hashrate_ghs, double hashprice_sats_per_ghs_s, int price_per_step, int step_size_bytes); | ||
| 28 | mining_client_stats_t *mining_get_or_create_client(uint32_t client_ip); | ||
| 29 | void mining_update_hashrate(uint32_t client_ip, bool accepted); | ||
| 30 | const mining_client_stats_t *mining_get_client_stats(uint32_t client_ip); | ||
| 31 | double mining_get_current_hashprice(void); | ||
| 32 | void mining_set_current_nbits(uint32_t nbits); | ||
| 33 | void mining_payment_init(void); | ||
| 34 | |||
| 35 | #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 @@ | |||
| 1 | #include "stratum_client.h" | ||
| 2 | #include "stratum_proxy.h" | ||
| 3 | #include "mining_payment.h" | ||
| 4 | #include "config.h" | ||
| 5 | #include "esp_log.h" | ||
| 6 | #include "esp_transport.h" | ||
| 7 | #include "esp_transport_tcp.h" | ||
| 8 | #include "cJSON.h" | ||
| 9 | #include "freertos/FreeRTOS.h" | ||
| 10 | #include "freertos/task.h" | ||
| 11 | #include <string.h> | ||
| 12 | #include <stdlib.h> | ||
| 13 | |||
| 14 | static const char *TAG = "stratum_client"; | ||
| 15 | static stratum_client_state_t s_state = {0}; | ||
| 16 | static esp_transport_handle_t s_transport = NULL; | ||
| 17 | static bool s_running = false; | ||
| 18 | static uint32_t s_req_id = 1; | ||
| 19 | static TaskHandle_t s_task_handle = NULL; | ||
| 20 | |||
| 21 | static int read_line(char *buf, int max_len) | ||
| 22 | { | ||
| 23 | int total = 0; | ||
| 24 | while (total < max_len - 1) { | ||
| 25 | int r = esp_transport_read(s_transport, buf + total, 1, 5000); | ||
| 26 | if (r <= 0) return -1; | ||
| 27 | if (buf[total] == '\n') { | ||
| 28 | buf[total + 1] = '\0'; | ||
| 29 | return total + 1; | ||
| 30 | } | ||
| 31 | total++; | ||
| 32 | } | ||
| 33 | buf[total] = '\0'; | ||
| 34 | return total; | ||
| 35 | } | ||
| 36 | |||
| 37 | static esp_err_t stratum_connect(const char *host, uint16_t port) | ||
| 38 | { | ||
| 39 | if (s_transport) { | ||
| 40 | esp_transport_close(s_transport); | ||
| 41 | esp_transport_destroy(s_transport); | ||
| 42 | s_transport = NULL; | ||
| 43 | } | ||
| 44 | |||
| 45 | s_transport = esp_transport_tcp_init(); | ||
| 46 | if (!s_transport) { | ||
| 47 | ESP_LOGE(TAG, "Failed to init TCP transport"); | ||
| 48 | return ESP_FAIL; | ||
| 49 | } | ||
| 50 | |||
| 51 | esp_err_t err = esp_transport_connect(s_transport, host, port, 10000); | ||
| 52 | if (err != ESP_OK) { | ||
| 53 | ESP_LOGE(TAG, "Failed to connect to %s:%u", host, (unsigned)port); | ||
| 54 | esp_transport_destroy(s_transport); | ||
| 55 | s_transport = NULL; | ||
| 56 | return ESP_FAIL; | ||
| 57 | } | ||
| 58 | |||
| 59 | strncpy(s_state.pool_host, host, sizeof(s_state.pool_host) - 1); | ||
| 60 | s_state.pool_port = port; | ||
| 61 | s_state.connected = true; | ||
| 62 | ESP_LOGI(TAG, "Connected to %s:%u", host, (unsigned)port); | ||
| 63 | return ESP_OK; | ||
| 64 | } | ||
| 65 | |||
| 66 | static void send_subscribe(void) | ||
| 67 | { | ||
| 68 | char subscribe[256]; | ||
| 69 | snprintf(subscribe, sizeof(subscribe), | ||
| 70 | "{\"id\":%lu,\"method\":\"mining.subscribe\",\"params\":[\"TollGate/1.0\"]}\n", | ||
| 71 | (unsigned long)s_req_id++); | ||
| 72 | esp_transport_write(s_transport, subscribe, strlen(subscribe), 5000); | ||
| 73 | ESP_LOGI(TAG, "Sent mining.subscribe"); | ||
| 74 | } | ||
| 75 | |||
| 76 | static void send_authorize(void) | ||
| 77 | { | ||
| 78 | const tollgate_config_t *cfg = tollgate_config_get(); | ||
| 79 | char authorize[512]; | ||
| 80 | snprintf(authorize, sizeof(authorize), | ||
| 81 | "{\"id\":%lu,\"method\":\"mining.authorize\",\"params\":[\"%s\",\"%s\"]}\n", | ||
| 82 | (unsigned long)s_req_id++, cfg->stratum_user, cfg->stratum_pass); | ||
| 83 | esp_transport_write(s_transport, authorize, strlen(authorize), 5000); | ||
| 84 | 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 | } | ||
| 93 | } | ||
| 94 | |||
| 95 | static void handle_mining_notify(cJSON *params) | ||
| 96 | { | ||
| 97 | if (!params || !cJSON_IsArray(params) || cJSON_GetArraySize(params) < 6) return; | ||
| 98 | |||
| 99 | cJSON *p_job_id = cJSON_GetArrayItem(params, 0); | ||
| 100 | cJSON *p_prevhash = cJSON_GetArrayItem(params, 1); | ||
| 101 | cJSON *p_version = cJSON_GetArrayItem(params, 5); | ||
| 102 | cJSON *p_nbits = cJSON_GetArrayItem(params, 6); | ||
| 103 | cJSON *p_ntime = cJSON_GetArrayItem(params, 7); | ||
| 104 | |||
| 105 | if (!p_job_id || !p_prevhash || !p_nbits) return; | ||
| 106 | |||
| 107 | stratum_job_t job = {0}; | ||
| 108 | job.job_id = (uint32_t)atoi(p_job_id->valuestring); | ||
| 109 | job.valid = true; | ||
| 110 | |||
| 111 | hex_to_bytes(p_prevhash->valuestring, job.prevhash, 32); | ||
| 112 | |||
| 113 | if (p_version && cJSON_IsString(p_version)) { | ||
| 114 | job.version = (uint32_t)strtoul(p_version->valuestring, NULL, 16); | ||
| 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 | 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); | ||
| 131 | } | ||
| 132 | |||
| 133 | static void handle_mining_set_difficulty(cJSON *params) | ||
| 134 | { | ||
| 135 | if (!params || !cJSON_IsArray(params) || cJSON_GetArraySize(params) < 1) return; | ||
| 136 | cJSON *diff = cJSON_GetArrayItem(params, 0); | ||
| 137 | if (diff && cJSON_IsNumber(diff)) { | ||
| 138 | s_state.difficulty = (uint64_t)diff->valuedouble; | ||
| 139 | ESP_LOGI(TAG, "Pool set difficulty: %llu", (unsigned long long)s_state.difficulty); | ||
| 140 | } | ||
| 141 | } | ||
| 142 | |||
| 143 | static void stratum_client_task(void *arg) | ||
| 144 | { | ||
| 145 | const tollgate_config_t *cfg = tollgate_config_get(); | ||
| 146 | |||
| 147 | while (s_running) { | ||
| 148 | if (!s_state.connected) { | ||
| 149 | esp_err_t err = stratum_connect(cfg->stratum_host, cfg->stratum_port); | ||
| 150 | if (err != ESP_OK) { | ||
| 151 | ESP_LOGW(TAG, "Connection failed, retrying in 10s..."); | ||
| 152 | vTaskDelay(pdMS_TO_TICKS(10000)); | ||
| 153 | continue; | ||
| 154 | } | ||
| 155 | send_subscribe(); | ||
| 156 | send_authorize(); | ||
| 157 | } | ||
| 158 | |||
| 159 | char recv_buf[2048]; | ||
| 160 | int len = read_line(recv_buf, sizeof(recv_buf)); | ||
| 161 | if (len <= 0) { | ||
| 162 | ESP_LOGW(TAG, "Connection lost"); | ||
| 163 | s_state.connected = false; | ||
| 164 | if (s_transport) { | ||
| 165 | esp_transport_close(s_transport); | ||
| 166 | esp_transport_destroy(s_transport); | ||
| 167 | s_transport = NULL; | ||
| 168 | } | ||
| 169 | vTaskDelay(pdMS_TO_TICKS(5000)); | ||
| 170 | continue; | ||
| 171 | } | ||
| 172 | |||
| 173 | cJSON *root = cJSON_Parse(recv_buf); | ||
| 174 | if (!root) continue; | ||
| 175 | |||
| 176 | cJSON *method = cJSON_GetObjectItemCaseSensitive(root, "method"); | ||
| 177 | if (method && cJSON_IsString(method)) { | ||
| 178 | cJSON *params = cJSON_GetObjectItemCaseSensitive(root, "params"); | ||
| 179 | |||
| 180 | if (strcmp(method->valuestring, "mining.notify") == 0) { | ||
| 181 | handle_mining_notify(params); | ||
| 182 | } else if (strcmp(method->valuestring, "mining.set_difficulty") == 0) { | ||
| 183 | handle_mining_set_difficulty(params); | ||
| 184 | } | ||
| 185 | } | ||
| 186 | |||
| 187 | cJSON *id = cJSON_GetObjectItemCaseSensitive(root, "id"); | ||
| 188 | cJSON *result = cJSON_GetObjectItemCaseSensitive(root, "result"); | ||
| 189 | cJSON *error = cJSON_GetObjectItemCaseSensitive(root, "error"); | ||
| 190 | |||
| 191 | if (id && result) { | ||
| 192 | if (cJSON_IsFalse(result) || (error && !cJSON_IsNull(error))) { | ||
| 193 | ESP_LOGW(TAG, "Request %d rejected", id->valueint); | ||
| 194 | } | ||
| 195 | } | ||
| 196 | |||
| 197 | cJSON_Delete(root); | ||
| 198 | } | ||
| 199 | |||
| 200 | if (s_transport) { | ||
| 201 | esp_transport_close(s_transport); | ||
| 202 | esp_transport_destroy(s_transport); | ||
| 203 | s_transport = NULL; | ||
| 204 | } | ||
| 205 | s_state.connected = false; | ||
| 206 | vTaskDelete(NULL); | ||
| 207 | } | ||
| 208 | |||
| 209 | esp_err_t stratum_client_init(void) | ||
| 210 | { | ||
| 211 | memset(&s_state, 0, sizeof(s_state)); | ||
| 212 | s_req_id = 1; | ||
| 213 | return ESP_OK; | ||
| 214 | } | ||
| 215 | |||
| 216 | esp_err_t stratum_client_start(void) | ||
| 217 | { | ||
| 218 | if (s_running) return ESP_OK; | ||
| 219 | s_running = true; | ||
| 220 | BaseType_t ret = xTaskCreate(stratum_client_task, "stratum_cli", 8192, NULL, 4, &s_task_handle); | ||
| 221 | if (ret != pdPASS) { | ||
| 222 | ESP_LOGE(TAG, "Failed to create stratum client task"); | ||
| 223 | s_running = false; | ||
| 224 | return ESP_FAIL; | ||
| 225 | } | ||
| 226 | ESP_LOGI(TAG, "Stratum client started"); | ||
| 227 | return ESP_OK; | ||
| 228 | } | ||
| 229 | |||
| 230 | void stratum_client_stop(void) | ||
| 231 | { | ||
| 232 | s_running = false; | ||
| 233 | if (s_task_handle) { | ||
| 234 | vTaskDelay(pdMS_TO_TICKS(1000)); | ||
| 235 | s_task_handle = NULL; | ||
| 236 | } | ||
| 237 | } | ||
| 238 | |||
| 239 | esp_err_t stratum_client_submit_share(uint32_t job_id, uint32_t nonce, uint32_t ntime, uint32_t version) | ||
| 240 | { | ||
| 241 | if (!s_state.connected || !s_transport) return ESP_FAIL; | ||
| 242 | |||
| 243 | const tollgate_config_t *cfg = tollgate_config_get(); | ||
| 244 | |||
| 245 | char submit[512]; | ||
| 246 | snprintf(submit, sizeof(submit), | ||
| 247 | "{\"id\":%lu,\"method\":\"mining.submit\",\"params\":[\"%s\",\"%lu\",\"%08lx\",\"%08lx\",\"%08lx\"]}\n", | ||
| 248 | (unsigned long)s_req_id++, cfg->stratum_user, | ||
| 249 | (unsigned long)job_id, (unsigned long)ntime, (unsigned long)nonce, (unsigned long)version); | ||
| 250 | |||
| 251 | int written = esp_transport_write(s_transport, submit, strlen(submit), 5000); | ||
| 252 | if (written < 0) { | ||
| 253 | ESP_LOGW(TAG, "Failed to submit share"); | ||
| 254 | s_state.shares_rejected++; | ||
| 255 | return ESP_FAIL; | ||
| 256 | } | ||
| 257 | |||
| 258 | s_state.shares_accepted++; | ||
| 259 | ESP_LOGI(TAG, "Share submitted: job=%lu nonce=%08lx", (unsigned long)job_id, (unsigned long)nonce); | ||
| 260 | return ESP_OK; | ||
| 261 | } | ||
| 262 | |||
| 263 | const stratum_client_state_t *stratum_client_get_state(void) | ||
| 264 | { | ||
| 265 | return &s_state; | ||
| 266 | } | ||
| 267 | |||
| 268 | void stratum_client_tick(void) | ||
| 269 | { | ||
| 270 | } | ||
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 @@ | |||
| 1 | #ifndef STRATUM_CLIENT_H | ||
| 2 | #define STRATUM_CLIENT_H | ||
| 3 | |||
| 4 | #include "esp_err.h" | ||
| 5 | #include "stratum_proxy.h" | ||
| 6 | #include <stdint.h> | ||
| 7 | #include <stdbool.h> | ||
| 8 | |||
| 9 | typedef struct { | ||
| 10 | bool connected; | ||
| 11 | char pool_host[128]; | ||
| 12 | uint16_t pool_port; | ||
| 13 | uint32_t nbits; | ||
| 14 | uint64_t difficulty; | ||
| 15 | uint64_t shares_accepted; | ||
| 16 | uint64_t shares_rejected; | ||
| 17 | bool sv2_active; | ||
| 18 | } stratum_client_state_t; | ||
| 19 | |||
| 20 | esp_err_t stratum_client_init(void); | ||
| 21 | esp_err_t stratum_client_start(void); | ||
| 22 | void stratum_client_stop(void); | ||
| 23 | esp_err_t stratum_client_submit_share(uint32_t job_id, uint32_t nonce, uint32_t ntime, uint32_t version); | ||
| 24 | const stratum_client_state_t *stratum_client_get_state(void); | ||
| 25 | void stratum_client_tick(void); | ||
| 26 | |||
| 27 | #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 @@ | |||
| 1 | #include "stratum_proxy.h" | ||
| 2 | #include "mining_payment.h" | ||
| 3 | #include "esp_log.h" | ||
| 4 | #include "lwip/sockets.h" | ||
| 5 | #include "freertos/FreeRTOS.h" | ||
| 6 | #include "freertos/task.h" | ||
| 7 | #include <string.h> | ||
| 8 | |||
| 9 | static const char *TAG = "stratum_proxy"; | ||
| 10 | static uint16_t s_port = 3333; | ||
| 11 | static bool s_running = false; | ||
| 12 | static TaskHandle_t s_task_handle = NULL; | ||
| 13 | static int s_server_fd = -1; | ||
| 14 | |||
| 15 | static stratum_job_t s_current_job = {0}; | ||
| 16 | static stratum_proxy_stats_t s_stats = {0}; | ||
| 17 | |||
| 18 | static void proxy_client_handler(void *arg) | ||
| 19 | { | ||
| 20 | int client_fd = (int)(intptr_t)arg; | ||
| 21 | struct sockaddr_in client_addr; | ||
| 22 | socklen_t addr_len = sizeof(client_addr); | ||
| 23 | getpeername(client_fd, (struct sockaddr *)&client_addr, &addr_len); | ||
| 24 | uint32_t client_ip = client_addr.sin_addr.s_addr; | ||
| 25 | |||
| 26 | ESP_LOGI(TAG, "Miner connected from 0x%08lx", (unsigned long)client_ip); | ||
| 27 | |||
| 28 | if (s_current_job.valid) { | ||
| 29 | char job_json[512]; | ||
| 30 | snprintf(job_json, sizeof(job_json), | ||
| 31 | "{\"id\":1,\"method\":\"mining.notify\",\"params\":[\"%lu\",\"%08lx%08lx%08lx%08lx%08lx%08lx%08lx%08lx\",\"\",\"\",\"\",\"%08lx\",\"%08lx\",\"%08lx\",true]}\n", | ||
| 32 | (unsigned long)s_current_job.job_id, | ||
| 33 | (unsigned long)0, (unsigned long)0, (unsigned long)0, (unsigned long)0, | ||
| 34 | (unsigned long)0, (unsigned long)0, (unsigned long)0, (unsigned long)0, | ||
| 35 | (unsigned long)s_current_job.nbits, (unsigned long)s_current_job.ntime, | ||
| 36 | (unsigned long)s_current_job.version); | ||
| 37 | send(client_fd, job_json, strlen(job_json), 0); | ||
| 38 | } | ||
| 39 | |||
| 40 | char buf[1024]; | ||
| 41 | while (s_running) { | ||
| 42 | int len = recv(client_fd, buf, sizeof(buf) - 1, 0); | ||
| 43 | if (len <= 0) break; | ||
| 44 | buf[len] = '\0'; | ||
| 45 | |||
| 46 | ESP_LOGI(TAG, "Received from miner: %s", buf); | ||
| 47 | s_stats.total_shares++; | ||
| 48 | s_stats.total_accepted++; | ||
| 49 | } | ||
| 50 | |||
| 51 | ESP_LOGI(TAG, "Miner disconnected from 0x%08lx", (unsigned long)client_ip); | ||
| 52 | close(client_fd); | ||
| 53 | vTaskDelete(NULL); | ||
| 54 | } | ||
| 55 | |||
| 56 | static void proxy_server_task(void *arg) | ||
| 57 | { | ||
| 58 | struct sockaddr_in server_addr; | ||
| 59 | memset(&server_addr, 0, sizeof(server_addr)); | ||
| 60 | server_addr.sin_family = AF_INET; | ||
| 61 | server_addr.sin_addr.s_addr = INADDR_ANY; | ||
| 62 | server_addr.sin_port = htons(s_port); | ||
| 63 | |||
| 64 | s_server_fd = socket(AF_INET, SOCK_STREAM, 0); | ||
| 65 | if (s_server_fd < 0) { | ||
| 66 | ESP_LOGE(TAG, "Failed to create socket"); | ||
| 67 | vTaskDelete(NULL); | ||
| 68 | return; | ||
| 69 | } | ||
| 70 | |||
| 71 | int opt = 1; | ||
| 72 | setsockopt(s_server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); | ||
| 73 | |||
| 74 | if (bind(s_server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) != 0) { | ||
| 75 | ESP_LOGE(TAG, "Failed to bind to port %u", (unsigned)s_port); | ||
| 76 | close(s_server_fd); | ||
| 77 | s_server_fd = -1; | ||
| 78 | vTaskDelete(NULL); | ||
| 79 | return; | ||
| 80 | } | ||
| 81 | |||
| 82 | if (listen(s_server_fd, 5) != 0) { | ||
| 83 | ESP_LOGE(TAG, "Failed to listen"); | ||
| 84 | close(s_server_fd); | ||
| 85 | s_server_fd = -1; | ||
| 86 | vTaskDelete(NULL); | ||
| 87 | return; | ||
| 88 | } | ||
| 89 | |||
| 90 | ESP_LOGI(TAG, "Stratum proxy listening on port %u", (unsigned)s_port); | ||
| 91 | |||
| 92 | while (s_running) { | ||
| 93 | struct sockaddr_in client_addr; | ||
| 94 | socklen_t client_len = sizeof(client_addr); | ||
| 95 | int client_fd = accept(s_server_fd, (struct sockaddr *)&client_addr, &client_len); | ||
| 96 | if (client_fd < 0) continue; | ||
| 97 | |||
| 98 | s_stats.active_miners++; | ||
| 99 | char task_name[16]; | ||
| 100 | snprintf(task_name, sizeof(task_name), "miner_%d", client_fd); | ||
| 101 | xTaskCreate(proxy_client_handler, task_name, 4096, (void *)(intptr_t)client_fd, 3, NULL); | ||
| 102 | } | ||
| 103 | |||
| 104 | close(s_server_fd); | ||
| 105 | s_server_fd = -1; | ||
| 106 | vTaskDelete(NULL); | ||
| 107 | } | ||
| 108 | |||
| 109 | esp_err_t stratum_proxy_init(uint16_t port) | ||
| 110 | { | ||
| 111 | s_port = port; | ||
| 112 | memset(&s_current_job, 0, sizeof(s_current_job)); | ||
| 113 | memset(&s_stats, 0, sizeof(s_stats)); | ||
| 114 | s_running = true; | ||
| 115 | |||
| 116 | BaseType_t ret = xTaskCreate(proxy_server_task, "stratum_proxy", 4096, NULL, 4, &s_task_handle); | ||
| 117 | if (ret != pdPASS) { | ||
| 118 | ESP_LOGE(TAG, "Failed to create proxy task"); | ||
| 119 | s_running = false; | ||
| 120 | return ESP_FAIL; | ||
| 121 | } | ||
| 122 | |||
| 123 | ESP_LOGI(TAG, "Stratum proxy initialized on port %u", (unsigned)port); | ||
| 124 | return ESP_OK; | ||
| 125 | } | ||
| 126 | |||
| 127 | void stratum_proxy_set_job(const stratum_job_t *job) | ||
| 128 | { | ||
| 129 | if (job) { | ||
| 130 | memcpy(&s_current_job, job, sizeof(stratum_job_t)); | ||
| 131 | s_stats.nbits = job->nbits; | ||
| 132 | s_stats.current_hashprice = mining_get_current_hashprice(); | ||
| 133 | } | ||
| 134 | } | ||
| 135 | |||
| 136 | const stratum_job_t *stratum_proxy_get_current_job(void) | ||
| 137 | { | ||
| 138 | return &s_current_job; | ||
| 139 | } | ||
| 140 | |||
| 141 | void stratum_proxy_get_stats(stratum_proxy_stats_t *stats) | ||
| 142 | { | ||
| 143 | if (stats) { | ||
| 144 | *stats = s_stats; | ||
| 145 | stats->current_hashprice = mining_get_current_hashprice(); | ||
| 146 | } | ||
| 147 | } | ||
| 148 | |||
| 149 | void stratum_proxy_stop(void) | ||
| 150 | { | ||
| 151 | s_running = false; | ||
| 152 | if (s_server_fd >= 0) { | ||
| 153 | close(s_server_fd); | ||
| 154 | s_server_fd = -1; | ||
| 155 | } | ||
| 156 | if (s_task_handle) { | ||
| 157 | vTaskDelay(pdMS_TO_TICKS(500)); | ||
| 158 | s_task_handle = NULL; | ||
| 159 | } | ||
| 160 | } | ||
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 @@ | |||
| 1 | #ifndef STRATUM_PROXY_H | ||
| 2 | #define STRATUM_PROXY_H | ||
| 3 | |||
| 4 | #include "esp_err.h" | ||
| 5 | #include <stdint.h> | ||
| 6 | #include <stdbool.h> | ||
| 7 | |||
| 8 | #define STRATUM_MAX_JOB_ID_LEN 32 | ||
| 9 | #define STRATUM_MAX_JOBS 4 | ||
| 10 | |||
| 11 | typedef struct { | ||
| 12 | uint32_t job_id; | ||
| 13 | uint8_t prevhash[32]; | ||
| 14 | uint8_t merkle_root[32]; | ||
| 15 | uint32_t ntime; | ||
| 16 | uint32_t nbits; | ||
| 17 | uint32_t version; | ||
| 18 | uint8_t target[32]; | ||
| 19 | int target_len; | ||
| 20 | bool valid; | ||
| 21 | } stratum_job_t; | ||
| 22 | |||
| 23 | typedef struct { | ||
| 24 | double hashrate_ghs; | ||
| 25 | uint32_t nbits; | ||
| 26 | uint64_t total_shares; | ||
| 27 | uint64_t total_accepted; | ||
| 28 | uint64_t total_rejected; | ||
| 29 | double current_hashprice; | ||
| 30 | int active_miners; | ||
| 31 | } stratum_proxy_stats_t; | ||
| 32 | |||
| 33 | esp_err_t stratum_proxy_init(uint16_t port); | ||
| 34 | void stratum_proxy_set_job(const stratum_job_t *job); | ||
| 35 | const stratum_job_t *stratum_proxy_get_current_job(void); | ||
| 36 | void stratum_proxy_get_stats(stratum_proxy_stats_t *stats); | ||
| 37 | void stratum_proxy_stop(void); | ||
| 38 | |||
| 39 | #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 @@ | |||
| 1 | #include "sw_miner.h" | ||
| 2 | #include "stratum_proxy.h" | ||
| 3 | #include "stratum_client.h" | ||
| 4 | #include "mining_payment.h" | ||
| 5 | #include "config.h" | ||
| 6 | #include "esp_log.h" | ||
| 7 | #include "mbedtls/sha256.h" | ||
| 8 | #include "freertos/FreeRTOS.h" | ||
| 9 | #include "freertos/task.h" | ||
| 10 | #include <string.h> | ||
| 11 | |||
| 12 | static const char *TAG = "sw_miner"; | ||
| 13 | static bool s_running = false; | ||
| 14 | static TaskHandle_t s_task_handle = NULL; | ||
| 15 | static double s_hashrate = 0.0; | ||
| 16 | |||
| 17 | static void sha256d(const uint8_t *data, size_t len, uint8_t *hash) | ||
| 18 | { | ||
| 19 | uint8_t tmp[32]; | ||
| 20 | mbedtls_sha256(data, len, tmp, 0); | ||
| 21 | mbedtls_sha256(tmp, 32, hash, 0); | ||
| 22 | } | ||
| 23 | |||
| 24 | static void sw_miner_task(void *arg) | ||
| 25 | { | ||
| 26 | ESP_LOGI(TAG, "Software miner started"); | ||
| 27 | |||
| 28 | uint64_t hashes = 0; | ||
| 29 | int64_t start_time = (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS; | ||
| 30 | |||
| 31 | uint8_t header[80]; | ||
| 32 | uint8_t hash[32]; | ||
| 33 | |||
| 34 | while (s_running) { | ||
| 35 | const stratum_job_t *job = stratum_proxy_get_current_job(); | ||
| 36 | if (!job || !job->valid) { | ||
| 37 | vTaskDelay(pdMS_TO_TICKS(1000)); | ||
| 38 | continue; | ||
| 39 | } | ||
| 40 | |||
| 41 | stratum_job_t local_job; | ||
| 42 | memcpy(&local_job, job, sizeof(stratum_job_t)); | ||
| 43 | |||
| 44 | memcpy(header, local_job.prevhash, 32); | ||
| 45 | memcpy(header + 32, local_job.merkle_root, 32); | ||
| 46 | |||
| 47 | uint32_t start_nonce = esp_random(); | ||
| 48 | uint32_t end_nonce = start_nonce + 1000; | ||
| 49 | |||
| 50 | for (uint32_t nonce = start_nonce; nonce < end_nonce && s_running; nonce++) { | ||
| 51 | header[76] = (nonce >> 0) & 0xFF; | ||
| 52 | header[77] = (nonce >> 8) & 0xFF; | ||
| 53 | header[78] = (nonce >> 16) & 0xFF; | ||
| 54 | header[79] = (nonce >> 24) & 0xFF; | ||
| 55 | |||
| 56 | sha256d(header, 80, hash); | ||
| 57 | hashes++; | ||
| 58 | |||
| 59 | if (memcmp(hash, local_job.target, local_job.target_len) <= 0) { | ||
| 60 | ESP_LOGI(TAG, "Valid share found! nonce=%08lx", (unsigned long)nonce); | ||
| 61 | stratum_client_submit_share(local_job.job_id, nonce, local_job.ntime, local_job.version); | ||
| 62 | mining_update_hashrate(0, true); | ||
| 63 | break; | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 67 | int64_t now = (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS; | ||
| 68 | int64_t elapsed_s = (now - start_time) / 1000; | ||
| 69 | if (elapsed_s > 0) { | ||
| 70 | s_hashrate = (double)hashes / (double)elapsed_s / 1e6; | ||
| 71 | } | ||
| 72 | |||
| 73 | taskYIELD(); | ||
| 74 | } | ||
| 75 | |||
| 76 | vTaskDelete(NULL); | ||
| 77 | } | ||
| 78 | |||
| 79 | esp_err_t sw_miner_start(void) | ||
| 80 | { | ||
| 81 | if (s_running) return ESP_OK; | ||
| 82 | s_running = true; | ||
| 83 | s_hashrate = 0.0; | ||
| 84 | |||
| 85 | BaseType_t ret = xTaskCreate(sw_miner_task, "sw_miner", 8192, NULL, 2, &s_task_handle); | ||
| 86 | if (ret != pdPASS) { | ||
| 87 | ESP_LOGE(TAG, "Failed to create sw_miner task"); | ||
| 88 | s_running = false; | ||
| 89 | return ESP_FAIL; | ||
| 90 | } | ||
| 91 | return ESP_OK; | ||
| 92 | } | ||
| 93 | |||
| 94 | void sw_miner_stop(void) | ||
| 95 | { | ||
| 96 | s_running = false; | ||
| 97 | if (s_task_handle) { | ||
| 98 | vTaskDelay(pdMS_TO_TICKS(500)); | ||
| 99 | s_task_handle = NULL; | ||
| 100 | } | ||
| 101 | } | ||
| 102 | |||
| 103 | bool sw_miner_is_running(void) | ||
| 104 | { | ||
| 105 | return s_running; | ||
| 106 | } | ||
| 107 | |||
| 108 | double sw_miner_get_hashrate(void) | ||
| 109 | { | ||
| 110 | return s_hashrate; | ||
| 111 | } | ||
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 @@ | |||
| 1 | #ifndef SW_MINER_H | ||
| 2 | #define SW_MINER_H | ||
| 3 | |||
| 4 | #include "esp_err.h" | ||
| 5 | #include <stdint.h> | ||
| 6 | #include <stdbool.h> | ||
| 7 | |||
| 8 | esp_err_t sw_miner_start(void); | ||
| 9 | void sw_miner_stop(void); | ||
| 10 | bool sw_miner_is_running(void); | ||
| 11 | double sw_miner_get_hashrate(void); | ||
| 12 | |||
| 13 | #endif | ||