ESP-Miner/components/stratum/stratum_api.c
mutatrum 6283480fa1
Parse stratum api reject reason (#472)
* Parse stratum api reject reason

* Remove superfluous nullcheck
2024-12-09 16:35:13 +01:00

397 lines
14 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/******************************************************************************
* *
* References:
* 1. Stratum Protocol - [link](https://reference.cash/mining/stratum-protocol)
*****************************************************************************/
#include "stratum_api.h"
#include "cJSON.h"
#include "esp_log.h"
#include "esp_ota_ops.h"
#include "lwip/sockets.h"
#include "utils.h"
#include <stdio.h>
#include <string.h>
#define BUFFER_SIZE 1024
static const char * TAG = "stratum_api";
static char * json_rpc_buffer = NULL;
static size_t json_rpc_buffer_size = 0;
// A message ID that must be unique per request that expects a response.
// For requests not expecting a response (called notifications), this is null.
static int send_uid = 1;
static void debug_stratum_tx(const char *);
int _parse_stratum_subscribe_result_message(const char * result_json_str, char ** extranonce, int * extranonce2_len);
void STRATUM_V1_reset_uid()
{
ESP_LOGI(TAG, "Resetting stratum uid");
send_uid = 1;
}
void STRATUM_V1_initialize_buffer()
{
json_rpc_buffer = malloc(BUFFER_SIZE);
json_rpc_buffer_size = BUFFER_SIZE;
if (json_rpc_buffer == NULL) {
printf("Error: Failed to allocate memory for buffer\n");
exit(1);
}
memset(json_rpc_buffer, 0, BUFFER_SIZE);
}
void cleanup_stratum_buffer()
{
free(json_rpc_buffer);
}
static void realloc_json_buffer(size_t len)
{
size_t old, new;
old = strlen(json_rpc_buffer);
new = old + len + 1;
if (new < json_rpc_buffer_size) {
return;
}
new = new + (BUFFER_SIZE - (new % BUFFER_SIZE));
void * new_sockbuf = realloc(json_rpc_buffer, new);
if (new_sockbuf == NULL) {
fprintf(stderr, "Error: realloc failed in recalloc_sock()\n");
ESP_LOGI(TAG, "Restarting System because of ERROR: realloc failed in recalloc_sock");
vTaskDelay(1000 / portTICK_PERIOD_MS);
esp_restart();
}
json_rpc_buffer = new_sockbuf;
memset(json_rpc_buffer + old, 0, new - old);
json_rpc_buffer_size = new;
}
char * STRATUM_V1_receive_jsonrpc_line(int sockfd)
{
if (json_rpc_buffer == NULL) {
STRATUM_V1_initialize_buffer();
}
char *line, *tok = NULL;
char recv_buffer[BUFFER_SIZE];
int nbytes;
size_t buflen = 0;
if (!strstr(json_rpc_buffer, "\n")) {
do {
memset(recv_buffer, 0, BUFFER_SIZE);
nbytes = recv(sockfd, recv_buffer, BUFFER_SIZE - 1, 0);
if (nbytes == -1) {
ESP_LOGI(TAG, "Error: recv (errno %d: %s)", errno, strerror(errno));
if (json_rpc_buffer) {
free(json_rpc_buffer);
json_rpc_buffer=0;
}
return 0;
}
realloc_json_buffer(nbytes);
strncat(json_rpc_buffer, recv_buffer, nbytes);
} while (!strstr(json_rpc_buffer, "\n"));
}
buflen = strlen(json_rpc_buffer);
tok = strtok(json_rpc_buffer, "\n");
line = strdup(tok);
int len = strlen(line);
if (buflen > len + 1)
memmove(json_rpc_buffer, json_rpc_buffer + len + 1, buflen - len + 1);
else
strcpy(json_rpc_buffer, "");
return line;
}
void STRATUM_V1_parse(StratumApiV1Message * message, const char * stratum_json)
{
cJSON * json = cJSON_Parse(stratum_json);
cJSON * id_json = cJSON_GetObjectItem(json, "id");
int64_t parsed_id = -1;
if (id_json != NULL && cJSON_IsNumber(id_json)) {
parsed_id = id_json->valueint;
}
message->message_id = parsed_id;
cJSON * method_json = cJSON_GetObjectItem(json, "method");
stratum_method result = STRATUM_UNKNOWN;
//if there is a method, then use that to decide what to do
if (method_json != NULL && cJSON_IsString(method_json)) {
if (strcmp("mining.notify", method_json->valuestring) == 0) {
result = MINING_NOTIFY;
} else if (strcmp("mining.set_difficulty", method_json->valuestring) == 0) {
result = MINING_SET_DIFFICULTY;
} else if (strcmp("mining.set_version_mask", method_json->valuestring) == 0) {
result = MINING_SET_VERSION_MASK;
} else if (strcmp("client.reconnect", method_json->valuestring) == 0) {
result = CLIENT_RECONNECT;
} else {
ESP_LOGI(TAG, "unhandled method in stratum message: %s", stratum_json);
}
//if there is no method, then it is a result
} else {
// parse results
cJSON * result_json = cJSON_GetObjectItem(json, "result");
cJSON * error_json = cJSON_GetObjectItem(json, "error");
cJSON * reject_reason_json = cJSON_GetObjectItem(json, "reject-reason");
//if the result is null, then it's a fail
if (result_json == NULL) {
message->response_success = false;
//if it's an error, then it's a fail
} else if (!cJSON_IsNull(error_json)) {
if (parsed_id < 5) {
result = STRATUM_RESULT_SETUP;
} else {
result = STRATUM_RESULT;
}
if (cJSON_IsArray(error_json)) {
int len = cJSON_GetArraySize(error_json);
if (len >= 2) {
cJSON * error_msg = cJSON_GetArrayItem(error_json, 1);
if (cJSON_IsString(error_msg)) {
message->error_str = strdup(cJSON_GetStringValue(error_msg));
}
}
}
message->response_success = false;
//if the result is a boolean, then parse it
} else if (cJSON_IsBool(result_json)) {
if (parsed_id < 5) {
result = STRATUM_RESULT_SETUP;
} else {
result = STRATUM_RESULT;
}
if (cJSON_IsTrue(result_json)) {
message->response_success = true;
} else {
message->response_success = false;
if (cJSON_IsString(reject_reason_json)) {
message->error_str = strdup(cJSON_GetStringValue(reject_reason_json));
}
}
//if the id is STRATUM_ID_SUBSCRIBE parse it
} else if (parsed_id == STRATUM_ID_SUBSCRIBE) {
result = STRATUM_RESULT_SUBSCRIBE;
cJSON * extranonce2_len_json = cJSON_GetArrayItem(result_json, 2);
if (extranonce2_len_json == NULL) {
ESP_LOGE(TAG, "Unable to parse extranonce2_len: %s", result_json->valuestring);
message->response_success = false;
goto done;
}
message->extranonce_2_len = extranonce2_len_json->valueint;
cJSON * extranonce_json = cJSON_GetArrayItem(result_json, 1);
if (extranonce_json == NULL) {
ESP_LOGE(TAG, "Unable parse extranonce: %s", result_json->valuestring);
message->response_success = false;
goto done;
}
message->extranonce_str = malloc(strlen(extranonce_json->valuestring) + 1);
strcpy(message->extranonce_str, extranonce_json->valuestring);
message->response_success = true;
//print the extranonce_str
ESP_LOGI(TAG, "extranonce_str: %s", message->extranonce_str);
ESP_LOGI(TAG, "extranonce_2_len: %d", message->extranonce_2_len);
//if the id is STRATUM_ID_CONFIGURE parse it
} else if (parsed_id == STRATUM_ID_CONFIGURE) {
cJSON * mask = cJSON_GetObjectItem(result_json, "version-rolling.mask");
if (mask != NULL) {
result = STRATUM_RESULT_VERSION_MASK;
message->version_mask = strtoul(mask->valuestring, NULL, 16);
ESP_LOGI(TAG, "Set version mask: %08lx", message->version_mask);
} else {
ESP_LOGI(TAG, "error setting version mask: %s", stratum_json);
}
} else {
ESP_LOGI(TAG, "unhandled result in stratum message: %s", stratum_json);
}
}
message->method = result;
if (message->method == MINING_NOTIFY) {
mining_notify * new_work = malloc(sizeof(mining_notify));
// new_work->difficulty = difficulty;
cJSON * params = cJSON_GetObjectItem(json, "params");
new_work->job_id = strdup(cJSON_GetArrayItem(params, 0)->valuestring);
new_work->prev_block_hash = strdup(cJSON_GetArrayItem(params, 1)->valuestring);
new_work->coinbase_1 = strdup(cJSON_GetArrayItem(params, 2)->valuestring);
new_work->coinbase_2 = strdup(cJSON_GetArrayItem(params, 3)->valuestring);
cJSON * merkle_branch = cJSON_GetArrayItem(params, 4);
new_work->n_merkle_branches = cJSON_GetArraySize(merkle_branch);
if (new_work->n_merkle_branches > MAX_MERKLE_BRANCHES) {
printf("Too many Merkle branches.\n");
abort();
}
new_work->merkle_branches = malloc(HASH_SIZE * new_work->n_merkle_branches);
for (size_t i = 0; i < new_work->n_merkle_branches; i++) {
hex2bin(cJSON_GetArrayItem(merkle_branch, i)->valuestring, new_work->merkle_branches + HASH_SIZE * i, HASH_SIZE);
}
new_work->version = strtoul(cJSON_GetArrayItem(params, 5)->valuestring, NULL, 16);
new_work->target = strtoul(cJSON_GetArrayItem(params, 6)->valuestring, NULL, 16);
new_work->ntime = strtoul(cJSON_GetArrayItem(params, 7)->valuestring, NULL, 16);
message->mining_notification = new_work;
// params can be varible length
int paramsLength = cJSON_GetArraySize(params);
int value = cJSON_IsTrue(cJSON_GetArrayItem(params, paramsLength - 1));
message->should_abandon_work = value;
} else if (message->method == MINING_SET_DIFFICULTY) {
cJSON * params = cJSON_GetObjectItem(json, "params");
uint32_t difficulty = cJSON_GetArrayItem(params, 0)->valueint;
message->new_difficulty = difficulty;
} else if (message->method == MINING_SET_VERSION_MASK) {
cJSON * params = cJSON_GetObjectItem(json, "params");
uint32_t version_mask = strtoul(cJSON_GetArrayItem(params, 0)->valuestring, NULL, 16);
message->version_mask = version_mask;
}
done:
cJSON_Delete(json);
}
void STRATUM_V1_free_mining_notify(mining_notify * params)
{
free(params->job_id);
free(params->prev_block_hash);
free(params->coinbase_1);
free(params->coinbase_2);
free(params->merkle_branches);
free(params);
}
int _parse_stratum_subscribe_result_message(const char * result_json_str, char ** extranonce, int * extranonce2_len)
{
cJSON * root = cJSON_Parse(result_json_str);
if (root == NULL) {
ESP_LOGE(TAG, "Unable to parse %s", result_json_str);
return -1;
}
cJSON * result = cJSON_GetObjectItem(root, "result");
if (result == NULL) {
ESP_LOGE(TAG, "Unable to parse subscribe result %s", result_json_str);
return -1;
}
cJSON * extranonce2_len_json = cJSON_GetArrayItem(result, 2);
if (extranonce2_len_json == NULL) {
ESP_LOGE(TAG, "Unable to parse extranonce2_len: %s", result->valuestring);
return -1;
}
*extranonce2_len = extranonce2_len_json->valueint;
cJSON * extranonce_json = cJSON_GetArrayItem(result, 1);
if (extranonce_json == NULL) {
ESP_LOGE(TAG, "Unable parse extranonce: %s", result->valuestring);
return -1;
}
*extranonce = malloc(strlen(extranonce_json->valuestring) + 1);
strcpy(*extranonce, extranonce_json->valuestring);
cJSON_Delete(root);
return 0;
}
int STRATUM_V1_subscribe(int socket, char * model)
{
// Subscribe
char subscribe_msg[BUFFER_SIZE];
const esp_app_desc_t *app_desc = esp_app_get_description();
const char *version = app_desc->version;
sprintf(subscribe_msg, "{\"id\": %d, \"method\": \"mining.subscribe\", \"params\": [\"bitaxe/%s/%s\"]}\n", send_uid++, model, version);
debug_stratum_tx(subscribe_msg);
return write(socket, subscribe_msg, strlen(subscribe_msg));
}
int STRATUM_V1_suggest_difficulty(int socket, uint32_t difficulty)
{
char difficulty_msg[BUFFER_SIZE];
sprintf(difficulty_msg, "{\"id\": %d, \"method\": \"mining.suggest_difficulty\", \"params\": [%ld]}\n", send_uid++, difficulty);
debug_stratum_tx(difficulty_msg);
return write(socket, difficulty_msg, strlen(difficulty_msg));
}
int STRATUM_V1_authenticate(int socket, const char * username, const char * pass)
{
char authorize_msg[BUFFER_SIZE];
sprintf(authorize_msg, "{\"id\": %d, \"method\": \"mining.authorize\", \"params\": [\"%s\", \"%s\"]}\n", send_uid++, username,
pass);
debug_stratum_tx(authorize_msg);
return write(socket, authorize_msg, strlen(authorize_msg));
}
/// @param socket Socket to write to
/// @param username The clients user name.
/// @param jobid The job ID for the work being submitted.
/// @param ntime The hex-encoded time value use in the block header.
/// @param extranonce_2 The hex-encoded value of extra nonce 2.
/// @param nonce The hex-encoded nonce value to use in the block header.
int STRATUM_V1_submit_share(int socket, const char * username, const char * jobid, const char * extranonce_2, const uint32_t ntime,
const uint32_t nonce, const uint32_t version)
{
char submit_msg[BUFFER_SIZE];
sprintf(submit_msg,
"{\"id\": %d, \"method\": \"mining.submit\", \"params\": [\"%s\", \"%s\", \"%s\", \"%08lx\", \"%08lx\", \"%08lx\"]}\n",
send_uid++, username, jobid, extranonce_2, ntime, nonce, version);
debug_stratum_tx(submit_msg);
return write(socket, submit_msg, strlen(submit_msg));
}
int STRATUM_V1_configure_version_rolling(int socket, uint32_t * version_mask)
{
char configure_msg[BUFFER_SIZE * 2];
sprintf(configure_msg,
"{\"id\": %d, \"method\": \"mining.configure\", \"params\": [[\"version-rolling\"], {\"version-rolling.mask\": "
"\"ffffffff\"}]}\n",
send_uid++);
debug_stratum_tx(configure_msg);
return write(socket, configure_msg, strlen(configure_msg));
}
static void debug_stratum_tx(const char * msg)
{
//remove the trailing newline
char * newline = strchr(msg, '\n');
if (newline != NULL) {
*newline = '\0';
}
ESP_LOGI(TAG, "tx: %s", msg);
//put it back!
if (newline != NULL) {
*newline = '\n';
}
}