upleb.uk

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

summaryrefslogtreecommitdiff
path: root/main/stratum_client.c
diff options
context:
space:
mode:
Diffstat (limited to 'main/stratum_client.c')
-rw-r--r--main/stratum_client.c270
1 files changed, 270 insertions, 0 deletions
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
14static const char *TAG = "stratum_client";
15static stratum_client_state_t s_state = {0};
16static esp_transport_handle_t s_transport = NULL;
17static bool s_running = false;
18static uint32_t s_req_id = 1;
19static TaskHandle_t s_task_handle = NULL;
20
21static 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
37static 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
66static 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
76static 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
87static 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
95static 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
133static 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
143static 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
209esp_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
216esp_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
230void 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
239esp_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
263const stratum_client_state_t *stratum_client_get_state(void)
264{
265 return &s_state;
266}
267
268void stratum_client_tick(void)
269{
270}