stratum_api: cleanup notify parsing

Split parsing into two steps, one for the method and one for the
params themselves. Currently it is up to the miner task to grab
the parameters in the format it needs.
This commit is contained in:
johnny9 2023-05-05 21:21:53 -04:00
parent abf914648f
commit 812e4213c1
6 changed files with 123 additions and 91 deletions

View File

@ -10,20 +10,18 @@
#define COINBASE2_SIZE 128
typedef struct {
uint32_t job_id;
uint8_t prev_block_hash[HASH_SIZE];
uint8_t coinbase_1[COINBASE_SIZE];
size_t coinbase_1_len;
uint8_t coinbase_2[COINBASE2_SIZE];
size_t coinbase_2_len;
uint8_t merkle_branches[MAX_MERKLE_BRANCHES][HASH_SIZE];
char * job_id;
char * prev_block_hash;
char * coinbase_1;
char * coinbase_2;
uint8_t * merkle_branches;
size_t n_merkle_branches;
uint32_t version;
uint32_t curtime;
uint32_t bits;
uint32_t target;
uint32_t nonce;
} work;
} mining_notify;
typedef enum {
STRATUM_UNKNOWN,
@ -31,23 +29,17 @@ typedef enum {
MINING_SET_DIFFICULTY
} stratum_method;
typedef struct {
int id;
stratum_method method;
char * method_str;
union {
work notify_work;
uint32_t notify_difficulty;
};
} stratum_message;
void initialize_stratum_buffer();
char * receive_jsonrpc_line(int sockfd);
int subscribe_to_stratum(int socket, char ** extranonce, int * extranonce2_len);
stratum_message parse_stratum_notify_message(const char * stratum_json);
stratum_method parse_stratum_method(const char * stratum_json);
mining_notify parse_mining_notify_message(const char * stratum_json);
void free_mining_notify(mining_notify params);
int parse_stratum_subscribe_result_message(const char * result_json_str,
char ** extranonce,

View File

@ -17,13 +17,13 @@ static int send_uid = 1;
void initialize_stratum_buffer()
{
json_rpc_buffer = malloc(BUFFER_SIZE); // Allocate memory dynamically
json_rpc_buffer = malloc(BUFFER_SIZE);
json_rpc_buffer_size = BUFFER_SIZE;
memset(json_rpc_buffer, 0, BUFFER_SIZE);
if (json_rpc_buffer == NULL)
{
printf("Error: Failed to allocate memory for buffer\n");
exit(1); // Handle error case
exit(1);
}
}
@ -56,6 +56,7 @@ static void realloc_json_buffer(size_t len)
json_rpc_buffer = new_sockbuf;
memset(json_rpc_buffer + old, 0, new - old);
json_rpc_buffer_size = new;
ESP_LOGI(TAG, "Current json rpc buffer size: %d", json_rpc_buffer_size);
}
char * receive_jsonrpc_line(int sockfd)
@ -96,58 +97,59 @@ char * receive_jsonrpc_line(int sockfd)
return line;
}
work parse_notify_work(cJSON * params) {
work new_work;
new_work.job_id = (uint32_t) strtoul(cJSON_GetArrayItem(params, 0)->valuestring, NULL, 16);
hex2bin(cJSON_GetArrayItem(params, 1)->valuestring, new_work.prev_block_hash, PREV_BLOCK_HASH_SIZE * 2);
stratum_method parse_stratum_method(const char * stratum_json)
{
cJSON * json = cJSON_Parse(stratum_json);
cJSON * method_json = cJSON_GetObjectItem(json, "method");
stratum_method result = STRATUM_UNKNOWN;
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;
}
}
char *coinb1 = cJSON_GetArrayItem(params, 2)->valuestring;
char *coinb2 = cJSON_GetArrayItem(params, 3)->valuestring;
hex2bin(coinb1, new_work.coinbase_1, strlen(coinb1) / 2);
new_work.coinbase_1_len = strlen(coinb1) / 2;
hex2bin(coinb2, new_work.coinbase_2, strlen(coinb2) / 2);
new_work.coinbase_2_len = strlen(coinb2) / 2;
cJSON_Delete(json);
return result;
}
cJSON *merkle_branch = cJSON_GetArrayItem(params, 4);
mining_notify parse_mining_notify_message(const char * stratum_json)
{
cJSON * json = cJSON_Parse(stratum_json);
cJSON * method = cJSON_GetObjectItem(json, "method");
if (method != NULL && cJSON_IsString(method)) {
assert(strcmp("mining.notify", method->valuestring) == 0);
}
mining_notify new_work;
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[i], PREV_BLOCK_HASH_SIZE * 2);
}
return new_work;
}
stratum_message parse_stratum_notify_message(const char * stratum_json)
{
stratum_message output;
output.method = STRATUM_UNKNOWN;
cJSON *json = cJSON_Parse(stratum_json);
cJSON *id = cJSON_GetObjectItem(json, "id");
cJSON *method = cJSON_GetObjectItem(json, "method");
if (id != NULL && cJSON_IsNumber(id)) {
output.id = id->valueint;
}
if (method != NULL && cJSON_IsString(method)) {
output.method_str = strdup(method->valuestring);
if (strcmp("mining.notify", method->valuestring) == 0) {
output.method = MINING_NOTIFY;
output.notify_work = parse_notify_work(cJSON_GetObjectItem(json, "params"));
} else if (strcmp("mining.set_difficulty", method->valuestring) == 0) {
output.method = MINING_SET_DIFFICULTY;
} else {
output.method = STRATUM_UNKNOWN;
}
hex2bin(cJSON_GetArrayItem(merkle_branch, i)->valuestring, new_work.merkle_branches + HASH_SIZE * i, HASH_SIZE * 2);
}
cJSON_Delete(json);
return output;
return new_work;
}
void free_mining_notify(mining_notify params)
{
free(params.prev_block_hash);
free(params.coinbase_1);
free(params.coinbase_2);
free(params.merkle_branches);
}
int parse_stratum_subscribe_result_message(const char * result_json_str,

View File

@ -1,7 +1,7 @@
#include "unity.h"
#include "stratum_api.h"
TEST_CASE("Check can parse json", "[mining.notify]")
TEST_CASE("Parse stratum method", "[mining.notify]")
{
const char * json_string = "{\"id\":null,\"method\":\"mining.notify\",\"params\":"
"[\"1b4c3d9041\","
@ -10,8 +10,24 @@ TEST_CASE("Check can parse json", "[mining.notify]")
"\"41903d4c1b2f736c7573682f0000000003ca890d27000000001976a9147c154ed1dc59609e3d26abb2df2ea3d587cd8c4188ac00000000000000002c6a4c2952534b424c4f434b3a4cb4cb2ddfc37c41baf5ef6b6b4899e3253a8f1dfc7e5dd68a5b5b27005014ef0000000000000000266a24aa21a9ed5caa249f1af9fbf71c986fea8e076ca34ae3514fb2f86400561b28c7b15949bf00000000\","
"[\"ae23055e00f0f697cc3640124812d96d4fe8bdfa03484c1c638ce5a1c0e9aa81\",\"980fb87cb61021dd7afd314fcb0dabd096f3d56a7377f6f320684652e7410a21\",\"a52e9868343c55ce405be8971ff340f562ae9ab6353f07140d01666180e19b52\",\"7435bdfa004e603953b2ed39f118803934d9cf17b06d979ceb682f2251bafac2\",\"2a91f061a22d27cb8f44eea79938fb241ebeb359891aa907f05ffde7ed44e52e\",\"302401f80eb5e958155135e25200bb8ea181ad2d05e804a531c7314d86403cdc\",\"318ecb6161eb9b4cfd802bd730e2d36c167ddf102e70aa7b4158e2870dd47392\",\"1114332a9858e0cf84b2425bb1e59eaabf91dd102d114aa443d57fc1b3beb0c9\",\"f43f38095c810613ed795a44d9fab02ff25269706f454885db9be05cdf9c06e1\",\"3e2fc26b27fddc39668b59099cd9635761bb72ed92404204e12bdff08b16fb75\",\"463c19427286342120039a83218fa87ce45448e246895abac11fff0036076758\",\"03d287f655813e540ddb9c4e7aeb922478662b0f5d8e9d0cbd564b20146bab76\"],"
"\"20000004\",\"1705c739\",\"64495522\",false]}";
stratum_message message = parse_stratum_notify_message(json_string);
TEST_ASSERT_EQUAL(MINING_NOTIFY, message.method);
stratum_method method = parse_stratum_method(json_string);
TEST_ASSERT_EQUAL(MINING_NOTIFY, method);
}
TEST_CASE("Parse stratum notify params", "[mining.notify]")
{
const char * json_string = "{\"id\":null,\"method\":\"mining.notify\",\"params\":"
"[\"1d2e0c4d3d\","
"\"ef4b9a48c7986466de4adc002f7337a6e121bc43000376ea0000000000000000\","
"\"01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4b03a5020cfabe6d6d379ae882651f6469f2ed6b8b40a4f9a4b41fd838a3ad6de8cba775f4e8f1d3080100000000000000\","
"\"41903d4c1b2f736c7573682f0000000003ca890d27000000001976a9147c154ed1dc59609e3d26abb2df2ea3d587cd8c4188ac00000000000000002c6a4c2952534b424c4f434b3a4cb4cb2ddfc37c41baf5ef6b6b4899e3253a8f1dfc7e5dd68a5b5b27005014ef0000000000000000266a24aa21a9ed5caa249f1af9fbf71c986fea8e076ca34ae3514fb2f86400561b28c7b15949bf00000000\","
"[\"ae23055e00f0f697cc3640124812d96d4fe8bdfa03484c1c638ce5a1c0e9aa81\",\"980fb87cb61021dd7afd314fcb0dabd096f3d56a7377f6f320684652e7410a21\",\"a52e9868343c55ce405be8971ff340f562ae9ab6353f07140d01666180e19b52\",\"7435bdfa004e603953b2ed39f118803934d9cf17b06d979ceb682f2251bafac2\",\"2a91f061a22d27cb8f44eea79938fb241ebeb359891aa907f05ffde7ed44e52e\",\"302401f80eb5e958155135e25200bb8ea181ad2d05e804a531c7314d86403cdc\",\"318ecb6161eb9b4cfd802bd730e2d36c167ddf102e70aa7b4158e2870dd47392\",\"1114332a9858e0cf84b2425bb1e59eaabf91dd102d114aa443d57fc1b3beb0c9\",\"f43f38095c810613ed795a44d9fab02ff25269706f454885db9be05cdf9c06e1\",\"3e2fc26b27fddc39668b59099cd9635761bb72ed92404204e12bdff08b16fb75\",\"463c19427286342120039a83218fa87ce45448e246895abac11fff0036076758\",\"03d287f655813e540ddb9c4e7aeb922478662b0f5d8e9d0cbd564b20146bab76\"],"
"\"20000004\",\"1705c739\",\"64495522\",false]}";
mining_notify params = parse_mining_notify_message(json_string);
TEST_ASSERT_EQUAL_STRING("1d2e0c4d3d", params.job_id);
TEST_ASSERT_EQUAL_STRING("ef4b9a48c7986466de4adc002f7337a6e121bc43000376ea0000000000000000", params.prev_block_hash);
TEST_ASSERT_EQUAL_STRING("01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4b03a5020cfabe6d6d379ae882651f6469f2ed6b8b40a4f9a4b41fd838a3ad6de8cba775f4e8f1d3080100000000000000", params.coinbase_1);
TEST_ASSERT_EQUAL_STRING("41903d4c1b2f736c7573682f0000000003ca890d27000000001976a9147c154ed1dc59609e3d26abb2df2ea3d587cd8c4188ac00000000000000002c6a4c2952534b424c4f434b3a4cb4cb2ddfc37c41baf5ef6b6b4899e3253a8f1dfc7e5dd68a5b5b27005014ef0000000000000000266a24aa21a9ed5caa249f1af9fbf71c986fea8e076ca34ae3514fb2f86400561b28c7b15949bf00000000", params.coinbase_2);
}
TEST_CASE("Test mining.subcribe result parsing", "[mining.subscribe]")

View File

@ -14,6 +14,7 @@
#include "lwip/err.h"
#include "lwip/sockets.h"
#include "stratum_api.h"
#include "mining.h"
#include "work_queue.h"
#include "system.h"
@ -39,15 +40,39 @@ TaskHandle_t serialTaskHandle = NULL;
static work_queue g_queue;
static char * extranonce_str = NULL;
static int extranonce_2_len = 0;
static void mining_task(void *pvParameters)
{
int termination_flag = 0;
while(true) {
work next_work = queue_dequeue(&g_queue, &termination_flag);
uint32_t free_heap_size = esp_get_free_heap_size();
ESP_LOGI(TAG, "miner heap free size: %u bytes", free_heap_size);
char * next_notify_json_str = queue_dequeue(&g_queue, &termination_flag);
ESP_LOGI(TAG, "New Work Dequeued");
// TODO: dequeue work from work_queue
ESP_LOGI(TAG, "Notify json: %s", next_notify_json_str);
mining_notify params = parse_mining_notify_message(next_notify_json_str);
char * coinbase_tx = construct_coinbase_tx(params.coinbase_1, params.coinbase_2,
extranonce_str, extranonce_2_len);
ESP_LOGI(TAG, "Coinbase tx: %s", coinbase_tx);
char * merkle_root = calculate_merkle_root_hash(coinbase_tx,
(uint8_t(*)[32]) params.merkle_branches,
params.n_merkle_branches);
ESP_LOGI(TAG, "Merkle root: %s", merkle_root);
free_mining_notify(params);
free(coinbase_tx);
free(merkle_root);
vPortFree(next_notify_json_str);
free_heap_size = esp_get_free_heap_size();
ESP_LOGI(TAG, "miner heap free size: %u bytes", free_heap_size);
// TODO: Construct the coinbase transaction and compute the merkle root
// TODO: Prepare the block header and start mining
// TODO: Increment the nonce
@ -108,26 +133,24 @@ static void admin_task(void *pvParameters)
auth_to_stratum(sock, STRATUM_USERNAME);
char * extranonce_str;
int extranonce_2_len;
subscribe_to_stratum(sock, &extranonce_str, &extranonce_2_len);
ESP_LOGI(TAG, "Extranonce: %s", extranonce_str);
ESP_LOGI(TAG, "Extranonce 2 length: %d", extranonce_2_len);
while (1)
{
char *line = receive_jsonrpc_line(sock);
// Error occurred during receiving
ESP_LOGI(TAG, "Json: %s", line);
stratum_message parsed_message = parse_stratum_notify_message(line);
if (parsed_message.method == STRATUM_UNKNOWN) {
ESP_LOGI(TAG, "UNKNOWN MESSAGE");
uint32_t free_heap_size = esp_get_free_heap_size();
ESP_LOGI(TAG, "before receive heap free size: %u bytes", free_heap_size);
char * line = receive_jsonrpc_line(sock);
free_heap_size = esp_get_free_heap_size();
ESP_LOGI(TAG, "after receive heap free size: %u bytes", free_heap_size);
stratum_method method = parse_stratum_method(line);
if (method == MINING_NOTIFY) {
queue_enqueue(&g_queue, line);
} else {
ESP_LOGI(TAG, "method: %s", parsed_message.method_str);
if (parsed_message.method == MINING_NOTIFY) {
queue_enqueue(&g_queue, parsed_message.notify_work);
}
vPortFree(line);
}
free(line);
}
if (sock != -1)

View File

@ -9,7 +9,7 @@ void queue_init(work_queue *queue) {
pthread_cond_init(&queue->not_full, NULL);
}
void queue_enqueue(work_queue *queue, work new_work) {
void queue_enqueue(work_queue *queue, char * new_work) {
pthread_mutex_lock(&queue->lock);
while (queue->count == QUEUE_SIZE) {
@ -24,19 +24,18 @@ void queue_enqueue(work_queue *queue, work new_work) {
pthread_mutex_unlock(&queue->lock);
}
work queue_dequeue(work_queue *queue, int *termination_flag) {
char * queue_dequeue(work_queue *queue, int *termination_flag) {
pthread_mutex_lock(&queue->lock);
while (queue->count == 0) {
if (*termination_flag) {
pthread_mutex_unlock(&queue->lock);
work empty_work = { 0 };
return empty_work;
return NULL;
}
pthread_cond_wait(&queue->not_empty, &queue->lock);
}
work next_work = queue->buffer[queue->head];
char * next_work = queue->buffer[queue->head];
queue->head = (queue->head + 1) % QUEUE_SIZE;
queue->count--;

View File

@ -4,10 +4,10 @@
#include <pthread.h>
#include "stratum_api.h"
#define QUEUE_SIZE 10
#define QUEUE_SIZE 3
typedef struct {
work buffer[QUEUE_SIZE];
char * buffer[QUEUE_SIZE];
int head;
int tail;
int count;
@ -17,7 +17,7 @@ typedef struct {
} work_queue;
void queue_init(work_queue *queue);
void queue_enqueue(work_queue *queue, work new_work);
work queue_dequeue(work_queue *queue, int *termination_flag);
void queue_enqueue(work_queue *queue, char * new_work);
char * queue_dequeue(work_queue *queue, int *termination_flag);
#endif // WORK_QUEUE_H