From d2c118833b8f0192f478315c1177a042e7a0dde9 Mon Sep 17 00:00:00 2001 From: Davide Grilli Date: Mon, 30 Mar 2026 01:07:19 +0200 Subject: [PATCH] feat(c-miner): port miner pipeline to modular C implementation --- block_builder.c | 495 ++++++++++++++++++++++++++++++++++++++++++++++++ block_builder.h | 40 ++++ config.c | 248 ++++++++++++++++++++++++ config.h | 32 ++++ json.c | 401 +++++++++++++++++++++++++++++++++++++++ json.h | 46 +++++ launcher.c | 422 +++++++++++++++++++++++++++++++++++++++++ main.c | 50 +++++ miner.c | 165 ++++++++++++++++ miner.h | 30 +++ mining_loop.c | 323 +++++++++++++++++++++++++++++++ mining_loop.h | 8 + rpc.c | 481 ++++++++++++++++++++++++++++++++++++++++++++++ rpc.h | 23 +++ types.c | 23 +++ types.h | 26 +++ utils.c | 449 +++++++++++++++++++++++++++++++++++++++++++ utils.h | 37 ++++ 18 files changed, 3299 insertions(+) create mode 100644 block_builder.c create mode 100644 block_builder.h create mode 100644 config.c create mode 100644 config.h create mode 100644 json.c create mode 100644 json.h create mode 100644 launcher.c create mode 100644 main.c create mode 100644 miner.c create mode 100644 miner.h create mode 100644 mining_loop.c create mode 100644 mining_loop.h create mode 100644 rpc.c create mode 100644 rpc.h create mode 100644 types.c create mode 100644 types.h create mode 100644 utils.c create mode 100644 utils.h diff --git a/block_builder.c b/block_builder.c new file mode 100644 index 0000000..d6cbfdf --- /dev/null +++ b/block_builder.c @@ -0,0 +1,495 @@ +#include "block_builder.h" + +#include +#include +#include + +#include "utils.h" + +static int append_u32_le_hex(StrBuf *sb, uint32_t v) { + return sb_append_byte_hex(sb, (uint8_t)(v & 0xFF)) && + sb_append_byte_hex(sb, (uint8_t)((v >> 8) & 0xFF)) && + sb_append_byte_hex(sb, (uint8_t)((v >> 16) & 0xFF)) && + sb_append_byte_hex(sb, (uint8_t)((v >> 24) & 0xFF)); +} + +static int append_u64_le_hex(StrBuf *sb, uint64_t v) { + int i; + for (i = 0; i < 8; i++) { + if (sb_append_byte_hex(sb, (uint8_t)((v >> (8 * i)) & 0xFF)) == 0) { + return 0; + } + } + return 1; +} + +static char *ascii_to_hex(const char *s) { + StrBuf sb; + size_t i; + + sb_init(&sb); + for (i = 0; s[i] != '\0'; ++i) { + if (sb_append_byte_hex(&sb, (uint8_t)s[i]) == 0) { + sb_free(&sb); + return NULL; + } + } + + return sb_take(&sb); +} + +char *tx_encode_coinbase_height(int height) { + uint8_t tmp[8]; + int n = 0; + int v; + StrBuf sb; + int i; + + if (height < 0) { + return NULL; + } + + if (height == 0) { + return strdup("00"); + } + + v = height; + while (v > 0) { + tmp[n++] = (uint8_t)(v & 0xFF); + v >>= 8; + } + + if (tmp[n - 1] & 0x80) { + tmp[n++] = 0x00; + } + + sb_init(&sb); + if (sb_append_byte_hex(&sb, (uint8_t)n) == 0) { + sb_free(&sb); + return NULL; + } + + for (i = 0; i < n; i++) { + if (sb_append_byte_hex(&sb, tmp[i]) == 0) { + sb_free(&sb); + return NULL; + } + } + + return sb_take(&sb); +} + +int is_segwit_tx(const char *raw_hex) { + size_t n; + + if (raw_hex == NULL) { + return 0; + } + + n = strlen(raw_hex); + if (n < 12) { + return 0; + } + + return raw_hex[8] == '0' && raw_hex[9] == '0' && raw_hex[10] == '0' && raw_hex[11] == '1'; +} + +int build_coinbase_transaction( + const BlockTemplate *tpl, + const char *miner_script_pubkey, + const char *extranonce1, + const char *extranonce2, + const char *coinbase_message, + char **coinbase_hex_out, + char **coinbase_txid_out +) { + int segwit; + StrBuf script_sig; + StrBuf tx; + StrBuf outputs; + char *height_hex; + char *msg_hex = NULL; + char *coinbase_hex; + char *core = NULL; + uint8_t *core_bytes = NULL; + size_t core_len = 0; + uint8_t txid_bin[32]; + uint8_t txid_rev[32]; + + *coinbase_hex_out = NULL; + *coinbase_txid_out = NULL; + + segwit = (tpl->default_witness_commitment != NULL && tpl->default_witness_commitment[0] != '\0'); + + height_hex = tx_encode_coinbase_height(tpl->height); + if (height_hex == NULL) { + return 0; + } + + sb_init(&script_sig); + if (sb_append(&script_sig, height_hex) == 0) { + free(height_hex); + sb_free(&script_sig); + return 0; + } + free(height_hex); + + if (coinbase_message != NULL && coinbase_message[0] != '\0') { + size_t mlen = strlen(coinbase_message); + msg_hex = ascii_to_hex(coinbase_message); + if (msg_hex == NULL) { + sb_free(&script_sig); + return 0; + } + if (sb_append(&script_sig, "6a") == 0 || + sb_append_byte_hex(&script_sig, (uint8_t)mlen) == 0 || + sb_append(&script_sig, msg_hex) == 0) { + free(msg_hex); + sb_free(&script_sig); + return 0; + } + free(msg_hex); + } + + if (sb_append(&script_sig, extranonce1) == 0 || sb_append(&script_sig, extranonce2) == 0) { + sb_free(&script_sig); + return 0; + } + + if ((script_sig.len / 2) > 100) { + fprintf(stderr, "[builder] scriptSig > 100 bytes\n"); + sb_free(&script_sig); + return 0; + } + + sb_init(&tx); + if (append_u32_le_hex(&tx, 2) == 0) { + sb_free(&script_sig); + sb_free(&tx); + return 0; + } + + if (segwit) { + if (sb_append(&tx, "0001") == 0) { + sb_free(&script_sig); + sb_free(&tx); + return 0; + } + } + + if (sb_append(&tx, "01") == 0 || + sb_append(&tx, "0000000000000000000000000000000000000000000000000000000000000000") == 0 || + sb_append(&tx, "ffffffff") == 0) { + sb_free(&script_sig); + sb_free(&tx); + return 0; + } + + if (encode_varint_hex((uint64_t)(script_sig.len / 2), &tx) == 0 || sb_append(&tx, script_sig.data) == 0) { + sb_free(&script_sig); + sb_free(&tx); + return 0; + } + + if (sb_append(&tx, "ffffffff") == 0) { + sb_free(&script_sig); + sb_free(&tx); + return 0; + } + + sb_free(&script_sig); + + sb_init(&outputs); + + if (append_u64_le_hex(&outputs, tpl->coinbase_value) == 0 || + encode_varint_hex((uint64_t)(strlen(miner_script_pubkey) / 2), &outputs) == 0 || + sb_append(&outputs, miner_script_pubkey) == 0) { + sb_free(&outputs); + sb_free(&tx); + return 0; + } + + if (segwit) { + const char *wc_raw = tpl->default_witness_commitment; + StrBuf wc_script; + + sb_init(&wc_script); + if (wc_raw[0] == '6' && wc_raw[1] == 'a') { + if (sb_append(&wc_script, wc_raw) == 0) { + sb_free(&wc_script); + sb_free(&outputs); + sb_free(&tx); + return 0; + } + } else { + if (sb_append(&wc_script, "6a24aa21a9ed") == 0 || sb_append(&wc_script, wc_raw) == 0) { + sb_free(&wc_script); + sb_free(&outputs); + sb_free(&tx); + return 0; + } + } + + if (append_u64_le_hex(&outputs, 0) == 0 || + encode_varint_hex((uint64_t)(wc_script.len / 2), &outputs) == 0 || + sb_append(&outputs, wc_script.data) == 0) { + sb_free(&wc_script); + sb_free(&outputs); + sb_free(&tx); + return 0; + } + + sb_free(&wc_script); + } + + if (encode_varint_hex(segwit ? 2 : 1, &tx) == 0 || sb_append(&tx, outputs.data) == 0) { + sb_free(&outputs); + sb_free(&tx); + return 0; + } + + sb_free(&outputs); + + if (segwit) { + if (sb_append(&tx, "0120") == 0 || + sb_append(&tx, "0000000000000000000000000000000000000000000000000000000000000000") == 0) { + sb_free(&tx); + return 0; + } + } + + if (sb_append(&tx, "00000000") == 0) { + sb_free(&tx); + return 0; + } + + coinbase_hex = sb_take(&tx); + + if (segwit) { + size_t core_len_hex; + size_t lock_start; + size_t body_len; + size_t no_wit_len; + + core_len_hex = 8 + (strlen(coinbase_hex) - 12); + core = (char *)malloc(core_len_hex + 1); + if (core == NULL) { + free(coinbase_hex); + return 0; + } + + memcpy(core, coinbase_hex, 8); + memcpy(core + 8, coinbase_hex + 12, strlen(coinbase_hex) - 12 + 1); + + lock_start = strlen(core) - 8; + body_len = lock_start; + if (body_len < 68) { + free(core); + free(coinbase_hex); + return 0; + } + no_wit_len = body_len - 68; + + memmove(core + no_wit_len, core + lock_start, 8); + core[no_wit_len + 8] = '\0'; + } else { + core = strdup(coinbase_hex); + if (core == NULL) { + free(coinbase_hex); + return 0; + } + } + + if (hex_to_bytes(core, &core_bytes, &core_len) == 0) { + free(core); + free(coinbase_hex); + return 0; + } + + double_sha256(core_bytes, core_len, txid_bin); + memcpy(txid_rev, txid_bin, 32); + reverse_bytes(txid_rev, 32); + + *coinbase_txid_out = bytes_to_hex(txid_rev, 32); + *coinbase_hex_out = coinbase_hex; + + free(core_bytes); + free(core); + + if (*coinbase_txid_out == NULL) { + free(*coinbase_hex_out); + *coinbase_hex_out = NULL; + return 0; + } + + return 1; +} + +char *calculate_merkle_root(const char *coinbase_txid_hex, const TemplateTransaction *txs, size_t tx_count) { + size_t leaf_count = tx_count + 1; + uint8_t *nodes; + size_t level_count; + size_t i; + + nodes = (uint8_t *)malloc(leaf_count * 32); + if (nodes == NULL) { + return NULL; + } + + { + uint8_t coinbase[32]; + if (hex_to_fixed_bytes(coinbase_txid_hex, coinbase, 32) == 0) { + free(nodes); + return NULL; + } + reverse_bytes(coinbase, 32); + memcpy(nodes, coinbase, 32); + } + + for (i = 0; i < tx_count; i++) { + uint8_t h[32]; + if (hex_to_fixed_bytes(txs[i].hash, h, 32) == 0) { + free(nodes); + return NULL; + } + reverse_bytes(h, 32); + memcpy(nodes + (i + 1) * 32, h, 32); + } + + level_count = leaf_count; + while (level_count > 1) { + size_t next_count = (level_count + 1) / 2; + uint8_t *next_nodes = (uint8_t *)malloc(next_count * 32); + + if (next_nodes == NULL) { + free(nodes); + return NULL; + } + + for (i = 0; i < next_count; i++) { + uint8_t pair[64]; + uint8_t dh[32]; + size_t left = i * 2; + size_t right = left + 1; + + memcpy(pair, nodes + left * 32, 32); + if (right < level_count) { + memcpy(pair + 32, nodes + right * 32, 32); + } else { + memcpy(pair + 32, nodes + left * 32, 32); + } + + double_sha256(pair, 64, dh); + memcpy(next_nodes + i * 32, dh, 32); + } + + free(nodes); + nodes = next_nodes; + level_count = next_count; + } + + reverse_bytes(nodes, 32); + { + char *root = bytes_to_hex(nodes, 32); + free(nodes); + return root; + } +} + +int build_block_header_bytes( + int version, + const char *prev_hash_hex, + const char *merkle_root_hex, + uint32_t timestamp, + const char *bits_hex, + uint32_t nonce, + uint8_t out_header[80] +) { + uint8_t prev[32]; + uint8_t merkle[32]; + uint8_t bits[4]; + + if (hex_to_fixed_bytes(prev_hash_hex, prev, 32) == 0) { + return 0; + } + if (hex_to_fixed_bytes(merkle_root_hex, merkle, 32) == 0) { + return 0; + } + if (hex_to_fixed_bytes(bits_hex, bits, 4) == 0) { + return 0; + } + + reverse_bytes(prev, 32); + reverse_bytes(merkle, 32); + reverse_bytes(bits, 4); + + out_header[0] = (uint8_t)(version & 0xFF); + out_header[1] = (uint8_t)((version >> 8) & 0xFF); + out_header[2] = (uint8_t)((version >> 16) & 0xFF); + out_header[3] = (uint8_t)((version >> 24) & 0xFF); + + memcpy(out_header + 4, prev, 32); + memcpy(out_header + 36, merkle, 32); + + out_header[68] = (uint8_t)(timestamp & 0xFF); + out_header[69] = (uint8_t)((timestamp >> 8) & 0xFF); + out_header[70] = (uint8_t)((timestamp >> 16) & 0xFF); + out_header[71] = (uint8_t)((timestamp >> 24) & 0xFF); + + memcpy(out_header + 72, bits, 4); + + out_header[76] = (uint8_t)(nonce & 0xFF); + out_header[77] = (uint8_t)((nonce >> 8) & 0xFF); + out_header[78] = (uint8_t)((nonce >> 16) & 0xFF); + out_header[79] = (uint8_t)((nonce >> 24) & 0xFF); + + return 1; +} + +char *build_block_header_hex( + int version, + const char *prev_hash_hex, + const char *merkle_root_hex, + uint32_t timestamp, + const char *bits_hex, + uint32_t nonce +) { + uint8_t header[80]; + + if (build_block_header_bytes(version, prev_hash_hex, merkle_root_hex, timestamp, bits_hex, nonce, header) == 0) { + return NULL; + } + + return bytes_to_hex(header, 80); +} + +char *serialize_block(const char *header_hex, const char *coinbase_tx_hex, const TemplateTransaction *txs, size_t tx_count) { + StrBuf sb; + size_t i; + + sb_init(&sb); + + if (sb_append(&sb, header_hex) == 0) { + sb_free(&sb); + return NULL; + } + + if (encode_varint_hex((uint64_t)(tx_count + 1), &sb) == 0) { + sb_free(&sb); + return NULL; + } + + if (sb_append(&sb, coinbase_tx_hex) == 0) { + sb_free(&sb); + return NULL; + } + + for (i = 0; i < tx_count; i++) { + if (txs[i].data == NULL || sb_append(&sb, txs[i].data) == 0) { + sb_free(&sb); + return NULL; + } + } + + return sb_take(&sb); +} diff --git a/block_builder.h b/block_builder.h new file mode 100644 index 0000000..7366cbf --- /dev/null +++ b/block_builder.h @@ -0,0 +1,40 @@ +#ifndef BLOCK_BUILDER_H +#define BLOCK_BUILDER_H + +#include +#include + +#include "types.h" + +char *tx_encode_coinbase_height(int height); +int is_segwit_tx(const char *raw_hex); +int build_coinbase_transaction( + const BlockTemplate *tpl, + const char *miner_script_pubkey, + const char *extranonce1, + const char *extranonce2, + const char *coinbase_message, + char **coinbase_hex_out, + char **coinbase_txid_out +); +char *calculate_merkle_root(const char *coinbase_txid_hex, const TemplateTransaction *txs, size_t tx_count); +int build_block_header_bytes( + int version, + const char *prev_hash_hex, + const char *merkle_root_hex, + uint32_t timestamp, + const char *bits_hex, + uint32_t nonce, + uint8_t out_header[80] +); +char *build_block_header_hex( + int version, + const char *prev_hash_hex, + const char *merkle_root_hex, + uint32_t timestamp, + const char *bits_hex, + uint32_t nonce +); +char *serialize_block(const char *header_hex, const char *coinbase_tx_hex, const TemplateTransaction *txs, size_t tx_count); + +#endif diff --git a/config.c b/config.c new file mode 100644 index 0000000..d230e08 --- /dev/null +++ b/config.c @@ -0,0 +1,248 @@ +#include "config.h" + +#include +#include +#include +#include +#include + +static char *trim(char *s) { + char *end; + + while (*s && isspace((unsigned char)*s)) { + ++s; + } + + if (*s == '\0') { + return s; + } + + end = s + strlen(s) - 1; + while (end > s && isspace((unsigned char)*end)) { + --end; + } + end[1] = '\0'; + return s; +} + +static int hex_is_valid_even(const char *s) { + size_t i; + size_t n = strlen(s); + + if (n == 0 || (n % 2) != 0) { + return 0; + } + + for (i = 0; i < n; ++i) { + if (isxdigit((unsigned char)s[i]) == 0) { + return 0; + } + } + + return 1; +} + +void config_set_defaults(MinerConfig *cfg) { + long ncpu; + + memset(cfg, 0, sizeof(*cfg)); + + snprintf(cfg->rpc_user, sizeof(cfg->rpc_user), "%s", "regtest"); + snprintf(cfg->rpc_password, sizeof(cfg->rpc_password), "%s", "regtest"); + snprintf(cfg->rpc_host, sizeof(cfg->rpc_host), "%s", "127.0.0.1"); + cfg->rpc_port = 18443; + + snprintf(cfg->wallet_address, sizeof(cfg->wallet_address), "%s", "bcrt1qn9ewln06n3xmc64278nj8m4nyde7ddeuvsvhw6"); + + cfg->difficulty_factor = 0.01; + snprintf(cfg->nonce_mode, sizeof(cfg->nonce_mode), "%s", "incremental"); + cfg->timestamp_update_interval = 30; + cfg->batch = 10000; + snprintf(cfg->coinbase_message, sizeof(cfg->coinbase_message), "%s", "/py-miner/"); + snprintf(cfg->extranonce1, sizeof(cfg->extranonce1), "%s", "1234567890abcdef"); + snprintf(cfg->extranonce2, sizeof(cfg->extranonce2), "%s", "12341234"); + + ncpu = sysconf(_SC_NPROCESSORS_ONLN); + cfg->num_processors = (ncpu > 0) ? (int)ncpu : 1; + + cfg->check_interval = 20; +} + +static int set_string_value(char *dst, size_t cap, const char *value, const char *key) { + size_t n = strlen(value); + + if (n >= cap) { + fprintf(stderr, "[config] %s troppo lungo\n", key); + return 0; + } + + memcpy(dst, value, n + 1); + return 1; +} + +static int parse_line(MinerConfig *cfg, const char *key, const char *value) { + if (strcmp(key, "RPC_USER") == 0) { + return set_string_value(cfg->rpc_user, sizeof(cfg->rpc_user), value, key); + } + if (strcmp(key, "RPC_PASSWORD") == 0) { + return set_string_value(cfg->rpc_password, sizeof(cfg->rpc_password), value, key); + } + if (strcmp(key, "RPC_HOST") == 0) { + return set_string_value(cfg->rpc_host, sizeof(cfg->rpc_host), value, key); + } + if (strcmp(key, "RPC_PORT") == 0) { + cfg->rpc_port = atoi(value); + return 1; + } + if (strcmp(key, "WALLET_ADDRESS") == 0) { + return set_string_value(cfg->wallet_address, sizeof(cfg->wallet_address), value, key); + } + if (strcmp(key, "DIFFICULTY_FACTOR") == 0) { + cfg->difficulty_factor = strtod(value, NULL); + return 1; + } + if (strcmp(key, "NONCE_MODE") == 0) { + return set_string_value(cfg->nonce_mode, sizeof(cfg->nonce_mode), value, key); + } + if (strcmp(key, "TIMESTAMP_UPDATE_INTERVAL") == 0) { + cfg->timestamp_update_interval = atoi(value); + return 1; + } + if (strcmp(key, "BATCH") == 0) { + long v = atol(value); + cfg->batch = (v > 0) ? (uint32_t)v : 0; + return 1; + } + if (strcmp(key, "COINBASE_MESSAGE") == 0) { + return set_string_value(cfg->coinbase_message, sizeof(cfg->coinbase_message), value, key); + } + if (strcmp(key, "EXTRANONCE1") == 0) { + return set_string_value(cfg->extranonce1, sizeof(cfg->extranonce1), value, key); + } + if (strcmp(key, "EXTRANONCE2") == 0) { + return set_string_value(cfg->extranonce2, sizeof(cfg->extranonce2), value, key); + } + if (strcmp(key, "NUM_PROCESSORS") == 0) { + cfg->num_processors = atoi(value); + return 1; + } + if (strcmp(key, "CHECK_INTERVAL") == 0) { + cfg->check_interval = atoi(value); + return 1; + } + + fprintf(stderr, "[config] chiave sconosciuta: %s\n", key); + return 0; +} + +bool config_validate(const MinerConfig *cfg) { + if (cfg->rpc_port <= 0) { + fprintf(stderr, "[config] RPC_PORT deve essere > 0\n"); + return false; + } + + if (cfg->batch == 0) { + fprintf(stderr, "[config] BATCH deve essere > 0\n"); + return false; + } + + if (cfg->timestamp_update_interval < 0) { + fprintf(stderr, "[config] TIMESTAMP_UPDATE_INTERVAL deve essere >= 0\n"); + return false; + } + + if (cfg->check_interval <= 0) { + fprintf(stderr, "[config] CHECK_INTERVAL deve essere > 0\n"); + return false; + } + + if (strcmp(cfg->nonce_mode, "incremental") == 0 || + strcmp(cfg->nonce_mode, "random") == 0 || + strcmp(cfg->nonce_mode, "mixed") == 0) { + /* ok */ + } else { + fprintf(stderr, "[config] NONCE_MODE non valido: %s\n", cfg->nonce_mode); + return false; + } + + if (hex_is_valid_even(cfg->extranonce1) == 0 || hex_is_valid_even(cfg->extranonce2) == 0) { + fprintf(stderr, "[config] EXTRANONCE1/EXTRANONCE2 devono essere hex con lunghezza pari\n"); + return false; + } + + if (cfg->wallet_address[0] == '\0') { + fprintf(stderr, "[config] WALLET_ADDRESS mancante\n"); + return false; + } + + return true; +} + +bool config_load(const char *path, MinerConfig *cfg) { + FILE *fp; + char line[512]; + + config_set_defaults(cfg); + + fp = fopen(path, "r"); + if (fp == NULL) { + perror("[config] fopen"); + return false; + } + + while (fgets(line, sizeof(line), fp) != NULL) { + char *eq; + char *key; + char *value; + char *comment; + size_t n; + + comment = strchr(line, '#'); + if (comment != NULL) { + *comment = '\0'; + } + + key = trim(line); + if (*key == '\0') { + continue; + } + + eq = strchr(key, '='); + if (eq == NULL) { + fprintf(stderr, "[config] riga non valida (manca '='): %s\n", key); + fclose(fp); + return false; + } + + *eq = '\0'; + value = trim(eq + 1); + key = trim(key); + + n = strlen(value); + if (n >= 2 && ((value[0] == '"' && value[n - 1] == '"') || (value[0] == '\'' && value[n - 1] == '\''))) { + value[n - 1] = '\0'; + ++value; + } + + if (parse_line(cfg, key, value) == 0) { + fclose(fp); + return false; + } + } + + fclose(fp); + + if (cfg->num_processors <= 0) { + long ncpu = sysconf(_SC_NPROCESSORS_ONLN); + cfg->num_processors = (ncpu > 0) ? (int)ncpu : 1; + } + + return config_validate(cfg); +} + +void config_print(const MinerConfig *cfg) { + printf("RPC: %s@%s:%d\n", cfg->rpc_user, cfg->rpc_host, cfg->rpc_port); + printf("Wallet: %s\n", cfg->wallet_address); + printf("Mining: mode=%s batch=%u diff_factor=%.8f\n", cfg->nonce_mode, cfg->batch, cfg->difficulty_factor); + printf("Workers: %d | check_interval=%d\n", cfg->num_processors, cfg->check_interval); +} diff --git a/config.h b/config.h new file mode 100644 index 0000000..f09d398 --- /dev/null +++ b/config.h @@ -0,0 +1,32 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#include +#include + +typedef struct { + char rpc_user[128]; + char rpc_password[128]; + char rpc_host[128]; + int rpc_port; + + char wallet_address[128]; + + double difficulty_factor; + char nonce_mode[16]; + int timestamp_update_interval; + uint32_t batch; + char coinbase_message[128]; + char extranonce1[128]; + char extranonce2[128]; + + int num_processors; + int check_interval; +} MinerConfig; + +void config_set_defaults(MinerConfig *cfg); +bool config_load(const char *path, MinerConfig *cfg); +bool config_validate(const MinerConfig *cfg); +void config_print(const MinerConfig *cfg); + +#endif diff --git a/json.c b/json.c new file mode 100644 index 0000000..7760d89 --- /dev/null +++ b/json.c @@ -0,0 +1,401 @@ +#include "json.h" + +#include +#include + +#define JSMN_ERROR_NOMEM -1 +#define JSMN_ERROR_INVAL -2 +#define JSMN_ERROR_PART -3 + +static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *tok; + + if (parser->toknext >= num_tokens) { + return NULL; + } + + tok = &tokens[parser->toknext++]; + tok->start = -1; + tok->end = -1; + tok->size = 0; + tok->parent = -1; + tok->type = JSMN_UNDEFINED; + return tok; +} + +static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, int start, int end) { + token->type = type; + token->start = start; + token->end = end; + token->size = 0; +} + +void jsmn_init(jsmn_parser *parser) { + parser->pos = 0; + parser->toknext = 0; + parser->toksuper = -1; +} + +static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, size_t len, jsmntok_t *tokens, size_t num_tokens) { + int start = (int)parser->pos; + + for (; parser->pos < len; parser->pos++) { + char c = js[parser->pos]; + if (c == ':' || c == '\t' || c == '\r' || c == '\n' || c == ' ' || c == ',' || c == ']' || c == '}') { + jsmntok_t *token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = (unsigned int)start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_PRIMITIVE, start, (int)parser->pos); + token->parent = parser->toksuper; + parser->pos--; + return 0; + } + + if (c < 32 || c >= 127) { + parser->pos = (unsigned int)start; + return JSMN_ERROR_INVAL; + } + } + + parser->pos = (unsigned int)start; + return JSMN_ERROR_PART; +} + +static int jsmn_parse_string(jsmn_parser *parser, const char *js, size_t len, jsmntok_t *tokens, size_t num_tokens) { + int start = (int)parser->pos; + + parser->pos++; + + for (; parser->pos < len; parser->pos++) { + char c = js[parser->pos]; + + if (c == '"') { + jsmntok_t *token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = (unsigned int)start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_STRING, start + 1, (int)parser->pos); + token->parent = parser->toksuper; + return 0; + } + + if (c == '\\' && parser->pos + 1 < len) { + char esc = js[parser->pos + 1]; + + if (esc == '"' || esc == '/' || esc == '\\' || esc == 'b' || esc == 'f' || esc == 'r' || esc == 'n' || esc == 't') { + parser->pos++; + continue; + } + + if (esc == 'u') { + int i; + parser->pos++; + for (i = 0; i < 4 && parser->pos + 1 < len; i++) { + char hc = js[parser->pos + 1]; + if (!((hc >= '0' && hc <= '9') || (hc >= 'A' && hc <= 'F') || (hc >= 'a' && hc <= 'f'))) { + parser->pos = (unsigned int)start; + return JSMN_ERROR_INVAL; + } + parser->pos++; + } + continue; + } + + parser->pos = (unsigned int)start; + return JSMN_ERROR_INVAL; + } + } + + parser->pos = (unsigned int)start; + return JSMN_ERROR_PART; +} + +int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, jsmntok_t *tokens, unsigned int num_tokens) { + int r; + unsigned int i; + + for (; parser->pos < len; parser->pos++) { + char c = js[parser->pos]; + + switch (c) { + case '{': + case '[': { + jsmntype_t type = (c == '{') ? JSMN_OBJECT : JSMN_ARRAY; + jsmntok_t *token = jsmn_alloc_token(parser, tokens, num_tokens); + + if (token == NULL) { + return JSMN_ERROR_NOMEM; + } + + if (parser->toksuper != -1) { + tokens[parser->toksuper].size++; + token->parent = parser->toksuper; + } + + jsmn_fill_token(token, type, (int)parser->pos, -1); + parser->toksuper = (int)(parser->toknext - 1); + break; + } + + case '}': + case ']': { + jsmntype_t type = (c == '}') ? JSMN_OBJECT : JSMN_ARRAY; + + for (i = parser->toknext; i > 0; i--) { + jsmntok_t *token = &tokens[i - 1]; + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + token->end = (int)parser->pos + 1; + parser->toksuper = token->parent; + break; + } + } + + if (i == 0) { + return JSMN_ERROR_INVAL; + } + break; + } + + case '"': + r = jsmn_parse_string(parser, js, len, tokens, num_tokens); + if (r < 0) { + return r; + } + if (parser->toksuper != -1) { + tokens[parser->toksuper].size++; + } + break; + + case '\t': + case '\r': + case '\n': + case ' ': + case ':': + case ',': + break; + + default: + r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); + if (r < 0) { + return r; + } + if (parser->toksuper != -1) { + tokens[parser->toksuper].size++; + } + break; + } + } + + for (i = parser->toknext; i > 0; i--) { + if (tokens[i - 1].start != -1 && tokens[i - 1].end == -1) { + return JSMN_ERROR_PART; + } + } + + return (int)parser->toknext; +} + +int json_doc_parse(JsonDoc *doc, const char *json) { + jsmn_parser p; + int r; + int cap = 1024; + + doc->json = json; + doc->tokens = NULL; + doc->token_count = 0; + + while (1) { + doc->tokens = (jsmntok_t *)malloc((size_t)cap * sizeof(jsmntok_t)); + if (doc->tokens == NULL) { + return 0; + } + + jsmn_init(&p); + r = jsmn_parse(&p, json, strlen(json), doc->tokens, (unsigned int)cap); + if (r >= 0) { + doc->token_count = r; + return 1; + } + + free(doc->tokens); + doc->tokens = NULL; + + if (r == JSMN_ERROR_NOMEM) { + cap *= 2; + if (cap > (1 << 21)) { + return 0; + } + continue; + } + + return 0; + } +} + +void json_doc_free(JsonDoc *doc) { + free(doc->tokens); + doc->tokens = NULL; + doc->token_count = 0; + doc->json = NULL; +} + +int json_token_streq(const JsonDoc *doc, int tok_idx, const char *s) { + const jsmntok_t *tok; + size_t len; + + if (tok_idx < 0 || tok_idx >= doc->token_count) { + return 0; + } + + tok = &doc->tokens[tok_idx]; + if (tok->type != JSMN_STRING && tok->type != JSMN_PRIMITIVE) { + return 0; + } + + len = (size_t)(tok->end - tok->start); + if (len != strlen(s)) { + return 0; + } + + return strncmp(doc->json + tok->start, s, len) == 0; +} + +int json_skip_token(const JsonDoc *doc, int tok_idx) { + int i; + int next; + + if (tok_idx < 0 || tok_idx >= doc->token_count) { + return -1; + } + + if (doc->tokens[tok_idx].type == JSMN_STRING || doc->tokens[tok_idx].type == JSMN_PRIMITIVE) { + return tok_idx + 1; + } + + if (doc->tokens[tok_idx].type == JSMN_ARRAY) { + next = tok_idx + 1; + for (i = 0; i < doc->tokens[tok_idx].size; i++) { + next = json_skip_token(doc, next); + if (next < 0) { + return -1; + } + } + return next; + } + + if (doc->tokens[tok_idx].type == JSMN_OBJECT) { + int pairs = doc->tokens[tok_idx].size / 2; + next = tok_idx + 1; + for (i = 0; i < pairs; i++) { + next = json_skip_token(doc, next); + if (next < 0) { + return -1; + } + next = json_skip_token(doc, next); + if (next < 0) { + return -1; + } + } + return next; + } + + return -1; +} + +int json_object_get(const JsonDoc *doc, int obj_idx, const char *key) { + int i; + int cur; + int pairs; + + if (obj_idx < 0 || obj_idx >= doc->token_count) { + return -1; + } + + if (doc->tokens[obj_idx].type != JSMN_OBJECT) { + return -1; + } + + pairs = doc->tokens[obj_idx].size / 2; + cur = obj_idx + 1; + for (i = 0; i < pairs; i++) { + int key_idx = cur; + int value_idx; + + cur = json_skip_token(doc, cur); + if (cur < 0) { + return -1; + } + + value_idx = cur; + cur = json_skip_token(doc, cur); + if (cur < 0) { + return -1; + } + + if (json_token_streq(doc, key_idx, key)) { + return value_idx; + } + } + + return -1; +} + +int json_array_get(const JsonDoc *doc, int arr_idx, int elem_index) { + int i; + int cur; + + if (arr_idx < 0 || arr_idx >= doc->token_count) { + return -1; + } + + if (doc->tokens[arr_idx].type != JSMN_ARRAY) { + return -1; + } + + if (elem_index < 0 || elem_index >= doc->tokens[arr_idx].size) { + return -1; + } + + cur = arr_idx + 1; + for (i = 0; i < elem_index; i++) { + cur = json_skip_token(doc, cur); + if (cur < 0) { + return -1; + } + } + + return cur; +} + +char *json_token_strdup(const JsonDoc *doc, int tok_idx) { + int start; + int end; + size_t len; + char *out; + + if (tok_idx < 0 || tok_idx >= doc->token_count) { + return NULL; + } + + start = doc->tokens[tok_idx].start; + end = doc->tokens[tok_idx].end; + if (start < 0 || end < start) { + return NULL; + } + + len = (size_t)(end - start); + out = (char *)malloc(len + 1); + if (out == NULL) { + return NULL; + } + + memcpy(out, doc->json + start, len); + out[len] = '\0'; + return out; +} diff --git a/json.h b/json.h new file mode 100644 index 0000000..b6ca9b6 --- /dev/null +++ b/json.h @@ -0,0 +1,46 @@ +#ifndef JSON_H +#define JSON_H + +#include + +typedef enum { + JSMN_UNDEFINED = 0, + JSMN_OBJECT = 1, + JSMN_ARRAY = 2, + JSMN_STRING = 3, + JSMN_PRIMITIVE = 4 +} jsmntype_t; + +typedef struct { + jsmntype_t type; + int start; + int end; + int size; + int parent; +} jsmntok_t; + +typedef struct { + unsigned int pos; + unsigned int toknext; + int toksuper; +} jsmn_parser; + +void jsmn_init(jsmn_parser *parser); +int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, jsmntok_t *tokens, unsigned int num_tokens); + +typedef struct { + const char *json; + jsmntok_t *tokens; + int token_count; +} JsonDoc; + +int json_doc_parse(JsonDoc *doc, const char *json); +void json_doc_free(JsonDoc *doc); + +int json_token_streq(const JsonDoc *doc, int tok_idx, const char *s); +int json_skip_token(const JsonDoc *doc, int tok_idx); +int json_object_get(const JsonDoc *doc, int obj_idx, const char *key); +int json_array_get(const JsonDoc *doc, int arr_idx, int elem_index); +char *json_token_strdup(const JsonDoc *doc, int tok_idx); + +#endif diff --git a/launcher.c b/launcher.c new file mode 100644 index 0000000..2e530be --- /dev/null +++ b/launcher.c @@ -0,0 +1,422 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "mining_loop.h" +#include "rpc.h" +#include "utils.h" + +typedef struct { + pid_t pid; + int fd; + char buf[4096]; + size_t len; +} WorkerProc; + +typedef struct { + double *rates; + long long *attempts; + char block_hash[130]; + int winner_idx; + double winner_rate; + int has_winner_rate; + int lines_printed; + double start_t; + double last_print; +} DashboardState; + +static double now_seconds(void) { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + return (double)ts.tv_sec + (double)ts.tv_nsec / 1e9; +} + +static void clear_lines(int n) { + int i; + for (i = 0; i < n; i++) { + fputs("\033[F\033[K", stdout); + } + fflush(stdout); +} + +static void dashboard_print(DashboardState *st, int n) { + int i; + double total_rate = 0.0; + long long total_attempts = 0; + time_t now_t = time(NULL); + char ts[64]; + + if (st->lines_printed > 0) { + clear_lines(st->lines_printed); + } + + for (i = 0; i < n; i++) { + total_rate += st->rates[i]; + total_attempts += st->attempts[i]; + } + + strftime(ts, sizeof(ts), "%Y-%m-%d %H:%M:%S", localtime(&now_t)); + + printf("%s | MINING STATUS\n", ts); + printf("========================================\n"); + printf("Total: %.2f kH/s | Attempts: %lld\n", total_rate, total_attempts); + printf("----------------------------------------\n"); + for (i = 0; i < n; i++) { + printf("Worker %-2d: %.2f kH/s | Attempts: %lld\n", i, st->rates[i], st->attempts[i]); + } + + st->lines_printed = 4 + n; + fflush(stdout); +} + +static int parse_event_line(const char *line, DashboardState *st, int n) { + int idx; + double rate; + long long attempts; + char hash[129]; + + if (sscanf(line, "status %d %lf %lld", &idx, &rate, &attempts) == 3) { + if (idx >= 0 && idx < n) { + st->rates[idx] = rate; + st->attempts[idx] = attempts; + } + return 0; + } + + if (sscanf(line, "found %d %lf", &idx, &rate) == 2) { + st->winner_idx = idx; + st->winner_rate = rate; + st->has_winner_rate = 1; + return 0; + } + + if (sscanf(line, "hash %d %128s", &idx, hash) == 2) { + snprintf(st->block_hash, sizeof(st->block_hash), "%s", hash); + return 0; + } + + if (sscanf(line, "submit %d", &idx) == 1) { + return 1; + } + + return 0; +} + +static int process_worker_bytes(WorkerProc *w, DashboardState *st, int n) { + char tmp[512]; + ssize_t r; + + r = read(w->fd, tmp, sizeof(tmp)); + if (r < 0) { + return 0; + } + + if (r == 0) { + close(w->fd); + w->fd = -1; + return 0; + } + + if (w->len + (size_t)r >= sizeof(w->buf)) { + w->len = 0; + } + + memcpy(w->buf + w->len, tmp, (size_t)r); + w->len += (size_t)r; + + while (1) { + char *nl = memchr(w->buf, '\n', w->len); + size_t line_len; + char line[512]; + int rc; + + if (nl == NULL) { + break; + } + + line_len = (size_t)(nl - w->buf); + if (line_len >= sizeof(line)) { + line_len = sizeof(line) - 1; + } + + memcpy(line, w->buf, line_len); + line[line_len] = '\0'; + + memmove(w->buf, nl + 1, w->len - (line_len + 1)); + w->len -= (line_len + 1); + + rc = parse_event_line(line, st, n); + if (rc == 1) { + return 1; + } + } + + return 0; +} + +static int aggregate_loop(WorkerProc *workers, int n) { + struct pollfd *pfds; + DashboardState st; + int i; + + memset(&st, 0, sizeof(st)); + st.rates = (double *)calloc((size_t)n, sizeof(double)); + st.attempts = (long long *)calloc((size_t)n, sizeof(long long)); + st.winner_idx = -1; + st.start_t = now_seconds(); + st.last_print = 0.0; + + if (st.rates == NULL || st.attempts == NULL) { + free(st.rates); + free(st.attempts); + return 0; + } + + pfds = (struct pollfd *)calloc((size_t)n, sizeof(struct pollfd)); + if (pfds == NULL) { + free(st.rates); + free(st.attempts); + return 0; + } + + while (1) { + int active = 0; + double now = now_seconds(); + int submit_seen = 0; + + for (i = 0; i < n; i++) { + pfds[i].fd = workers[i].fd; + pfds[i].events = (workers[i].fd >= 0) ? POLLIN : 0; + pfds[i].revents = 0; + if (workers[i].fd >= 0) { + active++; + } + } + + if (active == 0) { + break; + } + + poll(pfds, (nfds_t)n, 200); + + for (i = 0; i < n; i++) { + if (workers[i].fd >= 0 && (pfds[i].revents & POLLIN)) { + if (process_worker_bytes(&workers[i], &st, n)) { + submit_seen = 1; + } + } + if (workers[i].fd >= 0 && (pfds[i].revents & (POLLHUP | POLLERR | POLLNVAL))) { + close(workers[i].fd); + workers[i].fd = -1; + } + } + + if (now - st.last_print >= 1.0) { + dashboard_print(&st, n); + st.last_print = now; + } + + if (submit_seen) { + double elapsed = now_seconds() - st.start_t; + long long total_attempts = 0; + double avg_rate; + + if (st.lines_printed > 0) { + clear_lines(st.lines_printed); + } + + for (i = 0; i < n; i++) { + total_attempts += st.attempts[i]; + } + + avg_rate = (elapsed > 0.0) ? (double)total_attempts / elapsed / 1000.0 : 0.0; + + printf("==============================================================================\n"); + printf("[OK] BLOCK FOUND AND SUBMITTED\n"); + printf(" Hash: %s\n", st.block_hash[0] ? st.block_hash : "N/A"); + if (st.winner_idx >= 0) { + printf(" Worker: %d\n", st.winner_idx); + } + if (st.has_winner_rate) { + printf(" Worker hashrate: %.2f kH/s\n", st.winner_rate); + } + printf(" Average total hashrate: %.2f kH/s\n", avg_rate); + printf(" Total attempts: %lld\n", total_attempts); + printf("==============================================================================\n"); + + free(pfds); + free(st.rates); + free(st.attempts); + return 1; + } + } + + free(pfds); + free(st.rates); + free(st.attempts); + return 0; +} + +static void terminate_workers(WorkerProc *workers, int n) { + int i; + + for (i = 0; i < n; i++) { + if (workers[i].pid > 0) { + kill(workers[i].pid, SIGTERM); + } + } + + for (i = 0; i < n; i++) { + if (workers[i].pid > 0) { + waitpid(workers[i].pid, NULL, 0); + } + if (workers[i].fd >= 0) { + close(workers[i].fd); + workers[i].fd = -1; + } + } +} + +static int spawn_worker(WorkerProc *w, int idx, const MinerConfig *cfg, const char *base_ex2) { + int p[2]; + pid_t pid; + + if (pipe(p) < 0) { + return 0; + } + + pid = fork(); + if (pid < 0) { + close(p[0]); + close(p[1]); + return 0; + } + + if (pid == 0) { + char *ex2; + close(p[0]); + +#ifdef __linux__ + { + cpu_set_t set; + CPU_ZERO(&set); + CPU_SET(idx % (int)sysconf(_SC_NPROCESSORS_ONLN), &set); + sched_setaffinity(0, sizeof(set), &set); + } +#endif + + ex2 = hex_add_width(base_ex2, idx); + if (ex2 == NULL) { + _exit(1); + } + + srand((unsigned int)(time(NULL) ^ getpid())); + run_mining_loop(cfg, idx, ex2, p[1]); + free(ex2); + close(p[1]); + _exit(0); + } + + close(p[1]); + + w->pid = pid; + w->fd = p[0]; + w->len = 0; + return 1; +} + +static void usage(const char *prog) { + fprintf(stderr, "Uso: %s [--config miner.conf] [-n NUM_PROCS] [--base-extranonce2 HEX]\n", prog); +} + +int main(int argc, char **argv) { + const char *config_path = "miner.conf"; + int num_procs = -1; + const char *base_ex2_cli = NULL; + MinerConfig cfg; + RpcClient rpc; + char *chain = NULL; + char *base_ex2; + int i; + + for (i = 1; i < argc; i++) { + if ((strcmp(argv[i], "-n") == 0 || strcmp(argv[i], "--num-procs") == 0) && i + 1 < argc) { + num_procs = atoi(argv[++i]); + continue; + } + if (strcmp(argv[i], "--base-extranonce2") == 0 && i + 1 < argc) { + base_ex2_cli = argv[++i]; + continue; + } + if (strcmp(argv[i], "--config") == 0 && i + 1 < argc) { + config_path = argv[++i]; + continue; + } + usage(argv[0]); + return 1; + } + + if (config_load(config_path, &cfg) == 0) { + return 1; + } + + if (num_procs <= 0) { + num_procs = cfg.num_processors; + } + + base_ex2 = (char *)(base_ex2_cli ? base_ex2_cli : cfg.extranonce2); + + rpc_init(&rpc, &cfg); + if (rpc_test_connection(&rpc, &chain) == 0) { + fprintf(stderr, "[launcher] RPC test fallito\n"); + return 1; + } + + printf("\nStarting mining with %d processes (chain=%s, extranonce2 base=%s)\n\n", num_procs, chain, base_ex2); + free(chain); + + while (1) { + WorkerProc *workers = (WorkerProc *)calloc((size_t)num_procs, sizeof(WorkerProc)); + int ok = 1; + int restart; + + if (workers == NULL) { + return 1; + } + + for (i = 0; i < num_procs; i++) { + if (spawn_worker(&workers[i], i, &cfg, base_ex2) == 0) { + ok = 0; + break; + } + } + + if (ok == 0) { + fprintf(stderr, "[launcher] errore nello spawn worker\n"); + terminate_workers(workers, num_procs); + free(workers); + return 1; + } + + restart = aggregate_loop(workers, num_procs); + terminate_workers(workers, num_procs); + free(workers); + + if (restart == 0) { + break; + } + + printf("\nRestarting workers...\n\n"); + sleep(1); + } + + return 0; +} diff --git a/main.c b/main.c new file mode 100644 index 0000000..1b4ba1b --- /dev/null +++ b/main.c @@ -0,0 +1,50 @@ +#include +#include +#include +#include +#include + +#include "config.h" +#include "mining_loop.h" + +static void usage(const char *prog) { + fprintf(stderr, "Uso: %s [--config miner.conf] [--worker-idx N] [--extranonce2 HEX]\n", prog); +} + +int main(int argc, char **argv) { + const char *config_path = "miner.conf"; + int worker_idx = 0; + const char *ex2_override = NULL; + MinerConfig cfg; + int i; + + for (i = 1; i < argc; i++) { + if (strcmp(argv[i], "--config") == 0 && i + 1 < argc) { + config_path = argv[++i]; + continue; + } + if (strcmp(argv[i], "--worker-idx") == 0 && i + 1 < argc) { + worker_idx = atoi(argv[++i]); + continue; + } + if (strcmp(argv[i], "--extranonce2") == 0 && i + 1 < argc) { + ex2_override = argv[++i]; + continue; + } + + usage(argv[0]); + return 1; + } + + if (config_load(config_path, &cfg) == 0) { + return 1; + } + + if (ex2_override != NULL) { + snprintf(cfg.extranonce2, sizeof(cfg.extranonce2), "%s", ex2_override); + } + + srand((unsigned int)(time(NULL) ^ getpid())); + + return run_mining_loop(&cfg, worker_idx, cfg.extranonce2, -1); +} diff --git a/miner.c b/miner.c new file mode 100644 index 0000000..bd13932 --- /dev/null +++ b/miner.c @@ -0,0 +1,165 @@ +#include "miner.h" + +#include +#include +#include +#include +#include + +#include "utils.h" + +static int hash_meets_target(const uint8_t digest[32], const uint8_t target_be[32]) { + int i; + + for (i = 0; i < 32; i++) { + uint8_t hb = digest[31 - i]; + if (hb < target_be[i]) { + return 1; + } + if (hb > target_be[i]) { + return 0; + } + } + + return 1; +} + +static int compute_hash_batch( + const uint8_t header_76[76], + uint32_t start_nonce, + uint32_t batch_size, + const uint8_t target_be[32], + uint32_t *found_nonce, + uint8_t found_digest[32] +) { + SHA256_CTX base; + uint8_t tail[16]; + uint32_t i; + + SHA256_Init(&base); + SHA256_Update(&base, header_76, 64); + + memcpy(tail, header_76 + 64, 12); + + for (i = 0; i < batch_size; i++) { + SHA256_CTX ctx; + uint8_t digest1[32]; + uint8_t digest2[32]; + uint32_t n = start_nonce + i; + + tail[12] = (uint8_t)(n & 0xFF); + tail[13] = (uint8_t)((n >> 8) & 0xFF); + tail[14] = (uint8_t)((n >> 16) & 0xFF); + tail[15] = (uint8_t)((n >> 24) & 0xFF); + + ctx = base; + SHA256_Update(&ctx, tail, 16); + SHA256_Final(digest1, &ctx); + SHA256(digest1, 32, digest2); + + if (hash_meets_target(digest2, target_be)) { + *found_nonce = n; + memcpy(found_digest, digest2, 32); + return 1; + } + } + + return 0; +} + +static void write_u32_le(uint8_t *dst, uint32_t v) { + dst[0] = (uint8_t)(v & 0xFF); + dst[1] = (uint8_t)((v >> 8) & 0xFF); + dst[2] = (uint8_t)((v >> 16) & 0xFF); + dst[3] = (uint8_t)((v >> 24) & 0xFF); +} + +int mine_block( + uint8_t header_76[76], + const char *target_hex, + const char *nonce_mode, + uint32_t batch_size, + int timestamp_update_interval, + atomic_int *stop_flag, + mine_status_cb status_cb, + void *status_ctx, + MineResult *out +) { + uint8_t target_be[32]; + uint32_t nonce; + long long attempts = 0; + time_t t_start; + double last_rate_t; + long long last_rate_n = 0; + double last_tsu; + + memset(out, 0, sizeof(*out)); + + if (hex_to_fixed_bytes(target_hex, target_be, 32) == 0) { + fprintf(stderr, "[miner] target hex non valido\n"); + return 0; + } + + if (strcmp(nonce_mode, "incremental") == 0) { + nonce = 0; + } else { + nonce = (uint32_t)rand(); + } + + t_start = time(NULL); + last_rate_t = (double)t_start; + last_tsu = (double)t_start; + + while (1) { + uint32_t found_nonce = 0; + uint8_t found_digest[32]; + time_t now_t; + double now; + + if (atomic_load(stop_flag) != 0) { + return 0; + } + + now_t = time(NULL); + now = (double)now_t; + + if (timestamp_update_interval > 0 && (now - last_tsu) >= (double)timestamp_update_interval) { + write_u32_le(header_76 + 68, (uint32_t)now_t); + last_tsu = now; + } + + if (compute_hash_batch(header_76, nonce, batch_size, target_be, &found_nonce, found_digest)) { + double total = now - (double)t_start; + if (total <= 0.0) { + total = 1e-6; + } + + out->found = 1; + out->nonce = found_nonce; + out->attempts = attempts + batch_size; + out->hashrate_hz = (double)out->attempts / total; + + memcpy(out->header, header_76, 76); + write_u32_le(out->header + 76, found_nonce); + memcpy(out->digest, found_digest, 32); + return 1; + } + + attempts += batch_size; + nonce += batch_size; + + now = (double)time(NULL); + if ((now - last_rate_t) >= 2.0) { + double dt = now - last_rate_t; + if (dt <= 0.0) { + dt = 1e-6; + } + if (status_cb != NULL) { + double hr = (double)(attempts - last_rate_n) / dt; + status_cb(attempts, hr, status_ctx); + } + last_rate_t = now; + last_rate_n = attempts; + } + } +} diff --git a/miner.h b/miner.h new file mode 100644 index 0000000..b50002d --- /dev/null +++ b/miner.h @@ -0,0 +1,30 @@ +#ifndef MINER_H +#define MINER_H + +#include +#include + +typedef void (*mine_status_cb)(long long attempts, double hashrate_hz, void *ctx); + +typedef struct { + int found; + uint32_t nonce; + double hashrate_hz; + long long attempts; + uint8_t header[80]; + uint8_t digest[32]; +} MineResult; + +int mine_block( + uint8_t header_76[76], + const char *target_hex, + const char *nonce_mode, + uint32_t batch_size, + int timestamp_update_interval, + atomic_int *stop_flag, + mine_status_cb status_cb, + void *status_ctx, + MineResult *out +); + +#endif diff --git a/mining_loop.c b/mining_loop.c new file mode 100644 index 0000000..686304c --- /dev/null +++ b/mining_loop.c @@ -0,0 +1,323 @@ +#include "mining_loop.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "block_builder.h" +#include "miner.h" +#include "rpc.h" +#include "types.h" +#include "utils.h" + +typedef struct { + int event_fd; + int worker_idx; + int single_mode; +} StatusCtx; + +typedef struct { + RpcClient rpc; + atomic_int *stop_flag; + atomic_int *new_block_flag; + int check_interval; +} WatchdogCtx; + +static void emit_eventf(int fd, const char *fmt, ...) { + char line[512]; + va_list ap; + int n; + + if (fd < 0) { + return; + } + + va_start(ap, fmt); + n = vsnprintf(line, sizeof(line), fmt, ap); + va_end(ap); + + if (n > 0) { + size_t len = (size_t)n; + if (len > sizeof(line) - 1) { + len = sizeof(line) - 1; + } + write(fd, line, len); + } +} + +static void status_callback(long long attempts, double hashrate_hz, void *ctx_ptr) { + StatusCtx *ctx = (StatusCtx *)ctx_ptr; + + if (ctx->event_fd >= 0) { + emit_eventf(ctx->event_fd, "status %d %.6f %lld\n", ctx->worker_idx, hashrate_hz / 1000.0, attempts); + return; + } + + if (ctx->single_mode) { + printf("\r[main] hashrate: %.2f kH/s | attempts: %lld", hashrate_hz / 1000.0, attempts); + fflush(stdout); + } +} + +static void *watchdog_thread_main(void *arg) { + WatchdogCtx *ctx = (WatchdogCtx *)arg; + char *last_hash = NULL; + + if (rpc_get_best_block_hash(&ctx->rpc, &last_hash) == 0) { + return NULL; + } + + while (atomic_load(ctx->stop_flag) == 0) { + int i; + char *new_hash = NULL; + + for (i = 0; i < ctx->check_interval; i++) { + if (atomic_load(ctx->stop_flag) != 0) { + break; + } + sleep(1); + } + + if (atomic_load(ctx->stop_flag) != 0) { + break; + } + + if (rpc_get_best_block_hash(&ctx->rpc, &new_hash)) { + if (new_hash != NULL && last_hash != NULL && strcmp(new_hash, last_hash) != 0) { + atomic_store(ctx->new_block_flag, 1); + atomic_store(ctx->stop_flag, 1); + free(last_hash); + last_hash = new_hash; + break; + } + free(new_hash); + } + } + + free(last_hash); + return NULL; +} + +int run_mining_loop(const MinerConfig *cfg, int worker_idx, const char *extranonce2, int event_fd) { + RpcClient rpc; + char *chain = NULL; + char *miner_script = NULL; + StatusCtx status_ctx; + + rpc_init(&rpc, cfg); + + if (rpc_test_connection(&rpc, &chain) == 0) { + fprintf(stderr, "[main] connessione RPC fallita\n"); + return 1; + } + + if (rpc_get_address_script_pubkey(&rpc, cfg->wallet_address, &miner_script) == 0) { + fprintf(stderr, "[main] impossibile leggere scriptPubKey per wallet\n"); + free(chain); + return 1; + } + + fprintf(stdout, "[main] chain=%s worker=%d extranonce2=%s\n", chain, worker_idx, extranonce2); + + status_ctx.event_fd = event_fd; + status_ctx.worker_idx = worker_idx; + status_ctx.single_mode = (event_fd < 0) ? 1 : 0; + + while (1) { + BlockTemplate tpl; + char *coinbase_hex = NULL; + char *coinbase_txid = NULL; + char *target_hex = NULL; + char *merkle_root = NULL; + uint8_t header80[80]; + uint8_t header76[76]; + atomic_int stop_flag; + atomic_int new_block_flag; + WatchdogCtx wctx; + pthread_t watchdog; + MineResult result; + char *header_hex = NULL; + char *block_hash = NULL; + char *serialized = NULL; + int witness_count = 0; + size_t i; + + memset(&tpl, 0, sizeof(tpl)); + + if (rpc_get_block_template(&rpc, &tpl) == 0) { + fprintf(stderr, "[main] getblocktemplate fallita, retry in 5s\n"); + sleep(5); + continue; + } + + rpc_ensure_witness_data(&rpc, &tpl); + + for (i = 0; i < tpl.tx_count; i++) { + if (is_segwit_tx(tpl.transactions[i].data)) { + witness_count++; + } + } + + fprintf(stdout, "[main] template height=%d tx=%zu segwit=%d\n", tpl.height, tpl.tx_count, witness_count); + + if (build_coinbase_transaction( + &tpl, + miner_script, + cfg->extranonce1, + extranonce2, + cfg->coinbase_message, + &coinbase_hex, + &coinbase_txid + ) == 0) { + fprintf(stderr, "[main] build coinbase fallita\n"); + block_template_free(&tpl); + sleep(1); + continue; + } + + target_hex = calculate_target_hex(tpl.bits, cfg->difficulty_factor, chain); + merkle_root = calculate_merkle_root(coinbase_txid, tpl.transactions, tpl.tx_count); + if (target_hex == NULL || merkle_root == NULL) { + fprintf(stderr, "[main] target/merkle falliti\n"); + free(coinbase_hex); + free(coinbase_txid); + free(target_hex); + free(merkle_root); + block_template_free(&tpl); + sleep(1); + continue; + } + + if (build_block_header_bytes( + tpl.version, + tpl.previous_block_hash, + merkle_root, + (uint32_t)tpl.curtime, + tpl.bits, + 0, + header80 + ) == 0) { + fprintf(stderr, "[main] build header fallito\n"); + free(coinbase_hex); + free(coinbase_txid); + free(target_hex); + free(merkle_root); + block_template_free(&tpl); + sleep(1); + continue; + } + + memcpy(header76, header80, 76); + + atomic_init(&stop_flag, 0); + atomic_init(&new_block_flag, 0); + + wctx.check_interval = cfg->check_interval; + wctx.stop_flag = &stop_flag; + wctx.new_block_flag = &new_block_flag; + rpc_init(&wctx.rpc, cfg); + + pthread_create(&watchdog, NULL, watchdog_thread_main, &wctx); + + if (mine_block( + header76, + target_hex, + cfg->nonce_mode, + cfg->batch, + cfg->timestamp_update_interval, + &stop_flag, + status_callback, + &status_ctx, + &result + ) == 0) { + atomic_store(&stop_flag, 1); + pthread_join(watchdog, NULL); + + if (status_ctx.single_mode) { + putchar('\n'); + } + + if (atomic_load(&new_block_flag) != 0) { + fprintf(stdout, "[main] nuovo best block: riavvio ciclo\n"); + } + + free(coinbase_hex); + free(coinbase_txid); + free(target_hex); + free(merkle_root); + block_template_free(&tpl); + continue; + } + + atomic_store(&stop_flag, 1); + pthread_join(watchdog, NULL); + + if (status_ctx.single_mode) { + putchar('\n'); + } + + header_hex = bytes_to_hex(result.header, 80); + if (header_hex == NULL) { + free(coinbase_hex); + free(coinbase_txid); + free(target_hex); + free(merkle_root); + block_template_free(&tpl); + sleep(1); + continue; + } + + { + uint8_t dh[32]; + uint8_t rev[32]; + double_sha256(result.header, 80, dh); + memcpy(rev, dh, 32); + reverse_bytes(rev, 32); + block_hash = bytes_to_hex(rev, 32); + } + + if (block_hash != NULL) { + fprintf(stdout, "[main] block trovato: %s\n", block_hash); + emit_eventf(event_fd, "found %d %.6f\n", worker_idx, result.hashrate_hz / 1000.0); + emit_eventf(event_fd, "hash %d %s\n", worker_idx, block_hash); + } + + serialized = serialize_block(header_hex, coinbase_hex, tpl.transactions, tpl.tx_count); + if (serialized == NULL) { + fprintf(stderr, "[main] serializzazione blocco fallita\n"); + free(block_hash); + free(header_hex); + free(coinbase_hex); + free(coinbase_txid); + free(target_hex); + free(merkle_root); + block_template_free(&tpl); + sleep(1); + continue; + } + + rpc_submit_block(&rpc, serialized); + emit_eventf(event_fd, "submit %d\n", worker_idx); + + free(serialized); + free(block_hash); + free(header_hex); + free(coinbase_hex); + free(coinbase_txid); + free(target_hex); + free(merkle_root); + block_template_free(&tpl); + + sleep(1); + } + + free(chain); + free(miner_script); + return 0; +} diff --git a/mining_loop.h b/mining_loop.h new file mode 100644 index 0000000..88c619c --- /dev/null +++ b/mining_loop.h @@ -0,0 +1,8 @@ +#ifndef MINING_LOOP_H +#define MINING_LOOP_H + +#include "config.h" + +int run_mining_loop(const MinerConfig *cfg, int worker_idx, const char *extranonce2, int event_fd); + +#endif diff --git a/rpc.c b/rpc.c new file mode 100644 index 0000000..0033df7 --- /dev/null +++ b/rpc.c @@ -0,0 +1,481 @@ +#include "rpc.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "json.h" + +static int fd_write_all(int fd, const char *buf, size_t n) { + size_t off = 0; + while (off < n) { + ssize_t w = write(fd, buf + off, n - off); + if (w < 0) { + if (errno == EINTR) { + continue; + } + return 0; + } + off += (size_t)w; + } + return 1; +} + +static char *fd_read_all(int fd) { + size_t cap = 4096; + size_t len = 0; + char *out = (char *)malloc(cap); + + if (out == NULL) { + return NULL; + } + + while (1) { + ssize_t r; + if (len + 2048 + 1 > cap) { + char *p; + cap *= 2; + p = (char *)realloc(out, cap); + if (p == NULL) { + free(out); + return NULL; + } + out = p; + } + + r = read(fd, out + len, cap - len - 1); + if (r < 0) { + if (errno == EINTR) { + continue; + } + free(out); + return NULL; + } + + if (r == 0) { + break; + } + + len += (size_t)r; + } + + out[len] = '\0'; + return out; +} + +static int run_curl_rpc(const RpcClient *client, const char *payload, char **response_out) { + int in_pipe[2]; + int out_pipe[2]; + pid_t pid; + char auth[300]; + char url[300]; + int status; + char *response; + + if (pipe(in_pipe) < 0 || pipe(out_pipe) < 0) { + perror("pipe"); + return 0; + } + + pid = fork(); + if (pid < 0) { + perror("fork"); + close(in_pipe[0]); + close(in_pipe[1]); + close(out_pipe[0]); + close(out_pipe[1]); + return 0; + } + + if (pid == 0) { + close(in_pipe[1]); + close(out_pipe[0]); + + dup2(in_pipe[0], STDIN_FILENO); + dup2(out_pipe[1], STDOUT_FILENO); + dup2(out_pipe[1], STDERR_FILENO); + + close(in_pipe[0]); + close(out_pipe[1]); + + snprintf(auth, sizeof(auth), "%s:%s", client->cfg->rpc_user, client->cfg->rpc_password); + snprintf(url, sizeof(url), "http://%s:%d/", client->cfg->rpc_host, client->cfg->rpc_port); + + execlp( + "curl", + "curl", + "-s", + "--connect-timeout", + "5", + "--max-time", + "30", + "--user", + auth, + "--data-binary", + "@-", + "-H", + "content-type:text/plain;", + url, + (char *)NULL + ); + _exit(127); + } + + close(in_pipe[0]); + close(out_pipe[1]); + + if (fd_write_all(in_pipe[1], payload, strlen(payload)) == 0) { + close(in_pipe[1]); + close(out_pipe[0]); + kill(pid, SIGKILL); + waitpid(pid, NULL, 0); + return 0; + } + + close(in_pipe[1]); + + response = fd_read_all(out_pipe[0]); + close(out_pipe[0]); + + if (waitpid(pid, &status, 0) < 0) { + free(response); + return 0; + } + + if (WIFEXITED(status) == 0 || WEXITSTATUS(status) != 0) { + fprintf(stderr, "[rpc] curl fallita (status=%d)\n", status); + free(response); + return 0; + } + + if (response == NULL || response[0] == '\0') { + free(response); + return 0; + } + + *response_out = response; + return 1; +} + +static int token_is_null(const JsonDoc *doc, int tok_idx) { + return json_token_streq(doc, tok_idx, "null"); +} + +static int rpc_call_result(const RpcClient *client, const char *method, const char *params_json, char **result_out) { + char *payload; + size_t payload_len; + char *response = NULL; + JsonDoc doc; + int err_idx; + int res_idx; + char *result = NULL; + + payload_len = strlen(method) + strlen(params_json) + 128; + payload = (char *)malloc(payload_len); + if (payload == NULL) { + return 0; + } + + snprintf( + payload, + payload_len, + "{\"jsonrpc\":\"1.0\",\"id\":\"easy-miner\",\"method\":\"%s\",\"params\":%s}", + method, + params_json + ); + + if (run_curl_rpc(client, payload, &response) == 0) { + free(payload); + return 0; + } + free(payload); + + if (json_doc_parse(&doc, response) == 0) { + fprintf(stderr, "[rpc] JSON non valido in risposta a %s\n", method); + free(response); + return 0; + } + + if (doc.token_count <= 0 || doc.tokens[0].type != JSMN_OBJECT) { + json_doc_free(&doc); + free(response); + return 0; + } + + err_idx = json_object_get(&doc, 0, "error"); + if (err_idx >= 0 && token_is_null(&doc, err_idx) == 0) { + char *err_msg = json_token_strdup(&doc, err_idx); + fprintf(stderr, "[rpc] errore RPC %s: %s\n", method, err_msg ? err_msg : "(unknown)"); + free(err_msg); + json_doc_free(&doc); + free(response); + return 0; + } + + res_idx = json_object_get(&doc, 0, "result"); + if (res_idx < 0) { + json_doc_free(&doc); + free(response); + return 0; + } + + result = json_token_strdup(&doc, res_idx); + json_doc_free(&doc); + free(response); + + if (result == NULL) { + return 0; + } + + *result_out = result; + return 1; +} + +static long long token_to_ll(const JsonDoc *doc, int tok_idx) { + char *tmp = json_token_strdup(doc, tok_idx); + long long v = 0; + if (tmp != NULL) { + v = strtoll(tmp, NULL, 10); + free(tmp); + } + return v; +} + +void rpc_init(RpcClient *client, const MinerConfig *cfg) { + client->cfg = cfg; +} + +int rpc_test_connection(RpcClient *client, char **chain_out) { + char *result = NULL; + JsonDoc doc; + int chain_idx; + + if (rpc_call_result(client, "getblockchaininfo", "[]", &result) == 0) { + return 0; + } + + if (json_doc_parse(&doc, result) == 0) { + free(result); + return 0; + } + + chain_idx = json_object_get(&doc, 0, "chain"); + if (chain_idx < 0) { + json_doc_free(&doc); + free(result); + return 0; + } + + *chain_out = json_token_strdup(&doc, chain_idx); + + json_doc_free(&doc); + free(result); + + return (*chain_out != NULL); +} + +int rpc_get_best_block_hash(RpcClient *client, char **hash_out) { + return rpc_call_result(client, "getbestblockhash", "[]", hash_out); +} + +int rpc_get_address_script_pubkey(RpcClient *client, const char *wallet_address, char **script_out) { + char params[320]; + char *result = NULL; + JsonDoc doc; + int idx; + + snprintf(params, sizeof(params), "[\"%s\"]", wallet_address); + + if (rpc_call_result(client, "getaddressinfo", params, &result) == 0) { + return 0; + } + + if (json_doc_parse(&doc, result) == 0) { + free(result); + return 0; + } + + idx = json_object_get(&doc, 0, "scriptPubKey"); + if (idx < 0) { + json_doc_free(&doc); + free(result); + return 0; + } + + *script_out = json_token_strdup(&doc, idx); + + json_doc_free(&doc); + free(result); + return (*script_out != NULL); +} + +int rpc_get_raw_transaction(RpcClient *client, const char *txid, char **raw_tx_out) { + char params[256]; + snprintf(params, sizeof(params), "[\"%s\",false]", txid); + return rpc_call_result(client, "getrawtransaction", params, raw_tx_out); +} + +int rpc_get_block_template(RpcClient *client, BlockTemplate *tpl) { + char *result = NULL; + JsonDoc doc; + int idx_version; + int idx_prev; + int idx_curtime; + int idx_bits; + int idx_height; + int idx_coinbase; + int idx_wc; + int idx_txs; + int i; + + memset(tpl, 0, sizeof(*tpl)); + + if (rpc_call_result(client, "getblocktemplate", "[{\"rules\":[\"segwit\"]}]", &result) == 0) { + return 0; + } + + if (json_doc_parse(&doc, result) == 0) { + free(result); + return 0; + } + + idx_version = json_object_get(&doc, 0, "version"); + idx_prev = json_object_get(&doc, 0, "previousblockhash"); + idx_curtime = json_object_get(&doc, 0, "curtime"); + idx_bits = json_object_get(&doc, 0, "bits"); + idx_height = json_object_get(&doc, 0, "height"); + idx_coinbase = json_object_get(&doc, 0, "coinbasevalue"); + idx_wc = json_object_get(&doc, 0, "default_witness_commitment"); + idx_txs = json_object_get(&doc, 0, "transactions"); + + if (idx_version < 0 || idx_prev < 0 || idx_curtime < 0 || idx_bits < 0 || idx_height < 0 || idx_coinbase < 0 || idx_txs < 0) { + json_doc_free(&doc); + free(result); + return 0; + } + + tpl->version = (int)token_to_ll(&doc, idx_version); + tpl->curtime = (int)token_to_ll(&doc, idx_curtime); + tpl->height = (int)token_to_ll(&doc, idx_height); + tpl->coinbase_value = (uint64_t)token_to_ll(&doc, idx_coinbase); + + { + char *prev = json_token_strdup(&doc, idx_prev); + char *bits = json_token_strdup(&doc, idx_bits); + if (prev == NULL || bits == NULL) { + free(prev); + free(bits); + json_doc_free(&doc); + free(result); + return 0; + } + snprintf(tpl->previous_block_hash, sizeof(tpl->previous_block_hash), "%s", prev); + snprintf(tpl->bits, sizeof(tpl->bits), "%s", bits); + free(prev); + free(bits); + } + + if (idx_wc >= 0) { + tpl->default_witness_commitment = json_token_strdup(&doc, idx_wc); + } + + if (doc.tokens[idx_txs].type != JSMN_ARRAY) { + json_doc_free(&doc); + free(result); + return 0; + } + + tpl->tx_count = (size_t)doc.tokens[idx_txs].size; + tpl->transactions = (TemplateTransaction *)calloc(tpl->tx_count, sizeof(TemplateTransaction)); + if (tpl->transactions == NULL && tpl->tx_count > 0) { + json_doc_free(&doc); + free(result); + return 0; + } + + for (i = 0; i < doc.tokens[idx_txs].size; i++) { + int tx_idx = json_array_get(&doc, idx_txs, i); + int txid_idx; + int data_idx; + char *txid; + + if (tx_idx < 0 || doc.tokens[tx_idx].type != JSMN_OBJECT) { + json_doc_free(&doc); + free(result); + return 0; + } + + txid_idx = json_object_get(&doc, tx_idx, "txid"); + data_idx = json_object_get(&doc, tx_idx, "data"); + if (txid_idx < 0 || data_idx < 0) { + json_doc_free(&doc); + free(result); + return 0; + } + + txid = json_token_strdup(&doc, txid_idx); + tpl->transactions[i].data = json_token_strdup(&doc, data_idx); + if (txid == NULL || tpl->transactions[i].data == NULL) { + free(txid); + json_doc_free(&doc); + free(result); + return 0; + } + + snprintf(tpl->transactions[i].hash, sizeof(tpl->transactions[i].hash), "%s", txid); + free(txid); + } + + json_doc_free(&doc); + free(result); + return 1; +} + +int rpc_ensure_witness_data(RpcClient *client, BlockTemplate *tpl) { + size_t i; + + for (i = 0; i < tpl->tx_count; i++) { + char *raw = NULL; + if (rpc_get_raw_transaction(client, tpl->transactions[i].hash, &raw)) { + free(tpl->transactions[i].data); + tpl->transactions[i].data = raw; + } + } + + return 1; +} + +int rpc_submit_block(RpcClient *client, const char *serialized_block) { + char *params; + char *result = NULL; + size_t n; + int accepted = 0; + + n = strlen(serialized_block) + 16; + params = (char *)malloc(n); + if (params == NULL) { + return 0; + } + + snprintf(params, n, "[\"%s\"]", serialized_block); + + if (rpc_call_result(client, "submitblock", params, &result) == 0) { + free(params); + return 0; + } + + if (strcmp(result, "null") == 0) { + accepted = 1; + } else { + fprintf(stderr, "[rpc] submitblock: %s\n", result); + } + + free(result); + free(params); + return accepted; +} diff --git a/rpc.h b/rpc.h new file mode 100644 index 0000000..04b793c --- /dev/null +++ b/rpc.h @@ -0,0 +1,23 @@ +#ifndef RPC_H +#define RPC_H + +#include + +#include "config.h" +#include "types.h" + +typedef struct { + const MinerConfig *cfg; +} RpcClient; + +void rpc_init(RpcClient *client, const MinerConfig *cfg); + +int rpc_test_connection(RpcClient *client, char **chain_out); +int rpc_get_best_block_hash(RpcClient *client, char **hash_out); +int rpc_get_block_template(RpcClient *client, BlockTemplate *tpl); +int rpc_get_address_script_pubkey(RpcClient *client, const char *wallet_address, char **script_out); +int rpc_get_raw_transaction(RpcClient *client, const char *txid, char **raw_tx_out); +int rpc_ensure_witness_data(RpcClient *client, BlockTemplate *tpl); +int rpc_submit_block(RpcClient *client, const char *serialized_block); + +#endif diff --git a/types.c b/types.c new file mode 100644 index 0000000..7564cfb --- /dev/null +++ b/types.c @@ -0,0 +1,23 @@ +#include "types.h" + +#include + +void block_template_free(BlockTemplate *tpl) { + size_t i; + + if (tpl == NULL) { + return; + } + + for (i = 0; i < tpl->tx_count; ++i) { + free(tpl->transactions[i].data); + tpl->transactions[i].data = NULL; + } + + free(tpl->transactions); + tpl->transactions = NULL; + tpl->tx_count = 0; + + free(tpl->default_witness_commitment); + tpl->default_witness_commitment = NULL; +} diff --git a/types.h b/types.h new file mode 100644 index 0000000..1f8043c --- /dev/null +++ b/types.h @@ -0,0 +1,26 @@ +#ifndef TYPES_H +#define TYPES_H + +#include +#include + +typedef struct { + char hash[65]; + char *data; +} TemplateTransaction; + +typedef struct { + int version; + char previous_block_hash[65]; + int curtime; + char bits[9]; + int height; + uint64_t coinbase_value; + char *default_witness_commitment; + TemplateTransaction *transactions; + size_t tx_count; +} BlockTemplate; + +void block_template_free(BlockTemplate *tpl); + +#endif diff --git a/utils.c b/utils.c new file mode 100644 index 0000000..ccca70b --- /dev/null +++ b/utils.c @@ -0,0 +1,449 @@ +#include "utils.h" + +#include +#include +#include +#include +#include +#include +#include + +static char to_hex_digit(unsigned int v) { + if (v < 10U) { + return (char)('0' + v); + } + return (char)('a' + (v - 10U)); +} + +void sb_init(StrBuf *sb) { + sb->data = NULL; + sb->len = 0; + sb->cap = 0; +} + +void sb_free(StrBuf *sb) { + free(sb->data); + sb->data = NULL; + sb->len = 0; + sb->cap = 0; +} + +int sb_reserve(StrBuf *sb, size_t extra) { + size_t need; + size_t new_cap; + char *p; + + need = sb->len + extra + 1; + if (need <= sb->cap) { + return 1; + } + + new_cap = (sb->cap == 0) ? 128 : sb->cap; + while (new_cap < need) { + new_cap *= 2; + } + + p = (char *)realloc(sb->data, new_cap); + if (p == NULL) { + return 0; + } + + sb->data = p; + sb->cap = new_cap; + + if (sb->len == 0) { + sb->data[0] = '\0'; + } + + return 1; +} + +int sb_append(StrBuf *sb, const char *s) { + size_t n = strlen(s); + + if (sb_reserve(sb, n) == 0) { + return 0; + } + + memcpy(sb->data + sb->len, s, n + 1); + sb->len += n; + return 1; +} + +int sb_append_n(StrBuf *sb, const char *s, size_t n) { + if (sb_reserve(sb, n) == 0) { + return 0; + } + + memcpy(sb->data + sb->len, s, n); + sb->len += n; + sb->data[sb->len] = '\0'; + return 1; +} + +int sb_append_byte_hex(StrBuf *sb, uint8_t b) { + char tmp[3]; + + tmp[0] = to_hex_digit((unsigned int)(b >> 4)); + tmp[1] = to_hex_digit((unsigned int)(b & 0x0F)); + tmp[2] = '\0'; + return sb_append(sb, tmp); +} + +char *sb_take(StrBuf *sb) { + char *out = sb->data; + sb->data = NULL; + sb->len = 0; + sb->cap = 0; + return out; +} + +int hex_char_to_val(char c) { + if (c >= '0' && c <= '9') { + return c - '0'; + } + if (c >= 'a' && c <= 'f') { + return 10 + (c - 'a'); + } + if (c >= 'A' && c <= 'F') { + return 10 + (c - 'A'); + } + return -1; +} + +int hex_to_bytes(const char *hex, uint8_t **out, size_t *out_len) { + size_t n; + size_t i; + uint8_t *buf; + + if (hex == NULL || out == NULL || out_len == NULL) { + return 0; + } + + n = strlen(hex); + if ((n % 2) != 0) { + return 0; + } + + buf = (uint8_t *)malloc(n / 2); + if (buf == NULL) { + return 0; + } + + for (i = 0; i < n; i += 2) { + int hi = hex_char_to_val(hex[i]); + int lo = hex_char_to_val(hex[i + 1]); + if (hi < 0 || lo < 0) { + free(buf); + return 0; + } + buf[i / 2] = (uint8_t)((hi << 4) | lo); + } + + *out = buf; + *out_len = n / 2; + return 1; +} + +int hex_to_fixed_bytes(const char *hex, uint8_t *out, size_t out_len) { + size_t n; + size_t i; + + if (hex == NULL || out == NULL) { + return 0; + } + + n = strlen(hex); + if (n != out_len * 2) { + return 0; + } + + for (i = 0; i < n; i += 2) { + int hi = hex_char_to_val(hex[i]); + int lo = hex_char_to_val(hex[i + 1]); + if (hi < 0 || lo < 0) { + return 0; + } + out[i / 2] = (uint8_t)((hi << 4) | lo); + } + + return 1; +} + +char *bytes_to_hex(const uint8_t *data, size_t len) { + static const char tab[] = "0123456789abcdef"; + char *out; + size_t i; + + out = (char *)malloc(len * 2 + 1); + if (out == NULL) { + return NULL; + } + + for (i = 0; i < len; ++i) { + out[2 * i] = tab[data[i] >> 4]; + out[2 * i + 1] = tab[data[i] & 0x0F]; + } + out[len * 2] = '\0'; + + return out; +} + +void reverse_bytes(uint8_t *data, size_t n) { + size_t i; + + for (i = 0; i < n / 2; ++i) { + uint8_t t = data[i]; + data[i] = data[n - 1 - i]; + data[n - 1 - i] = t; + } +} + +void double_sha256(const uint8_t *data, size_t len, uint8_t out[32]) { + uint8_t tmp[SHA256_DIGEST_LENGTH]; + + SHA256(data, len, tmp); + SHA256(tmp, SHA256_DIGEST_LENGTH, out); +} + +int encode_varint_hex(uint64_t value, StrBuf *sb) { + uint8_t bytes[9]; + size_t n = 0; + size_t i; + + if (value < 0xFD) { + bytes[n++] = (uint8_t)value; + } else if (value <= 0xFFFFULL) { + bytes[n++] = 0xFD; + bytes[n++] = (uint8_t)(value & 0xFF); + bytes[n++] = (uint8_t)((value >> 8) & 0xFF); + } else if (value <= 0xFFFFFFFFULL) { + bytes[n++] = 0xFE; + bytes[n++] = (uint8_t)(value & 0xFF); + bytes[n++] = (uint8_t)((value >> 8) & 0xFF); + bytes[n++] = (uint8_t)((value >> 16) & 0xFF); + bytes[n++] = (uint8_t)((value >> 24) & 0xFF); + } else { + bytes[n++] = 0xFF; + bytes[n++] = (uint8_t)(value & 0xFF); + bytes[n++] = (uint8_t)((value >> 8) & 0xFF); + bytes[n++] = (uint8_t)((value >> 16) & 0xFF); + bytes[n++] = (uint8_t)((value >> 24) & 0xFF); + bytes[n++] = (uint8_t)((value >> 32) & 0xFF); + bytes[n++] = (uint8_t)((value >> 40) & 0xFF); + bytes[n++] = (uint8_t)((value >> 48) & 0xFF); + bytes[n++] = (uint8_t)((value >> 56) & 0xFF); + } + + for (i = 0; i < n; ++i) { + if (sb_append_byte_hex(sb, bytes[i]) == 0) { + return 0; + } + } + + return 1; +} + +static char *bn_to_fixed_hex64(const BIGNUM *bn) { + char *tmp; + char *out; + size_t n; + size_t i; + + tmp = BN_bn2hex(bn); + if (tmp == NULL) { + return NULL; + } + + n = strlen(tmp); + if (n > 64) { + OPENSSL_free(tmp); + return NULL; + } + + out = (char *)malloc(65); + if (out == NULL) { + OPENSSL_free(tmp); + return NULL; + } + + for (i = 0; i < 64; ++i) { + out[i] = '0'; + } + out[64] = '\0'; + + for (i = 0; i < n; ++i) { + char c = tmp[i]; + if (isupper((unsigned char)c)) { + c = (char)tolower((unsigned char)c); + } + out[64 - n + i] = c; + } + + OPENSSL_free(tmp); + return out; +} + +char *decode_nbits_hex(uint32_t nbits) { + BN_CTX *ctx; + BIGNUM *bn; + BIGNUM *sig; + char *out; + uint32_t exponent; + uint32_t significand; + + exponent = (nbits >> 24) & 0xFF; + significand = nbits & 0x007FFFFF; + + ctx = BN_CTX_new(); + bn = BN_new(); + sig = BN_new(); + + if (ctx == NULL || bn == NULL || sig == NULL) { + BN_CTX_free(ctx); + BN_free(bn); + BN_free(sig); + return NULL; + } + + BN_set_word(sig, significand); + BN_copy(bn, sig); + + if (exponent >= 3) { + BN_lshift(bn, bn, 8 * ((int)exponent - 3)); + } else { + BN_rshift(bn, bn, 8 * (3 - (int)exponent)); + } + + out = bn_to_fixed_hex64(bn); + + BN_CTX_free(ctx); + BN_free(bn); + BN_free(sig); + return out; +} + +char *calculate_target_hex(const char *bits_hex, double difficulty_factor, const char *network) { + static const char *MAX_TARGET_HEX = "00000000ffff0000000000000000000000000000000000000000000000000000"; + BIGNUM *max_bn; + BIGNUM *limit_bn; + BIGNUM *num_bn; + BIGNUM *den_bn; + BIGNUM *result_bn; + BN_CTX *ctx; + char *original; + char *out; + uint64_t scaled_factor; + const uint64_t SCALE = 1000000000ULL; + uint32_t nbits; + + if (bits_hex == NULL || network == NULL) { + return NULL; + } + + nbits = (uint32_t)strtoul(bits_hex, NULL, 16); + original = decode_nbits_hex(nbits); + if (original == NULL) { + return NULL; + } + + if (strcmp(network, "regtest") == 0) { + if (difficulty_factor < 0.0) { + difficulty_factor = 0.1; + } + } else { + difficulty_factor = 1.0; + } + + if (difficulty_factor == 0.0) { + return original; + } + + scaled_factor = (uint64_t)llround(difficulty_factor * (double)SCALE); + if (scaled_factor == 0ULL) { + scaled_factor = 1ULL; + } + + ctx = BN_CTX_new(); + max_bn = BN_new(); + limit_bn = BN_new(); + num_bn = BN_new(); + den_bn = BN_new(); + result_bn = BN_new(); + + if (ctx == NULL || max_bn == NULL || limit_bn == NULL || num_bn == NULL || den_bn == NULL || result_bn == NULL) { + BN_CTX_free(ctx); + BN_free(max_bn); + BN_free(limit_bn); + BN_free(num_bn); + BN_free(den_bn); + BN_free(result_bn); + free(original); + return NULL; + } + + BN_hex2bn(&max_bn, MAX_TARGET_HEX); + BN_hex2bn(&limit_bn, "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + + BN_copy(num_bn, max_bn); + BN_mul_word(num_bn, (BN_ULONG)SCALE); + BN_set_word(den_bn, (BN_ULONG)scaled_factor); + BN_div(result_bn, NULL, num_bn, den_bn, ctx); + + if (BN_cmp(result_bn, limit_bn) > 0) { + BN_copy(result_bn, limit_bn); + } + + out = bn_to_fixed_hex64(result_bn); + + BN_CTX_free(ctx); + BN_free(max_bn); + BN_free(limit_bn); + BN_free(num_bn); + BN_free(den_bn); + BN_free(result_bn); + free(original); + + return out; +} + +char *hex_add_width(const char *base_hex, int add_value) { + static const char *hex_digits = "0123456789abcdef"; + char *out; + size_t n; + int carry; + size_t pos; + + if (base_hex == NULL || add_value < 0) { + return NULL; + } + + n = strlen(base_hex); + if (n == 0) { + return NULL; + } + + out = strdup(base_hex); + if (out == NULL) { + return NULL; + } + + carry = add_value; + pos = n; + while (pos > 0 && carry > 0) { + int digit = hex_char_to_val(out[pos - 1]); + int sum; + + if (digit < 0) { + free(out); + return NULL; + } + + sum = digit + carry; + out[pos - 1] = hex_digits[sum & 0x0F]; + carry = sum >> 4; + --pos; + } + + return out; +} diff --git a/utils.h b/utils.h new file mode 100644 index 0000000..14f8e48 --- /dev/null +++ b/utils.h @@ -0,0 +1,37 @@ +#ifndef UTILS_H +#define UTILS_H + +#include +#include + +#include "config.h" + +typedef struct { + char *data; + size_t len; + size_t cap; +} StrBuf; + +void sb_init(StrBuf *sb); +void sb_free(StrBuf *sb); +int sb_reserve(StrBuf *sb, size_t extra); +int sb_append(StrBuf *sb, const char *s); +int sb_append_n(StrBuf *sb, const char *s, size_t n); +int sb_append_byte_hex(StrBuf *sb, uint8_t b); +char *sb_take(StrBuf *sb); + +int hex_char_to_val(char c); +int hex_to_bytes(const char *hex, uint8_t **out, size_t *out_len); +char *bytes_to_hex(const uint8_t *data, size_t len); +int hex_to_fixed_bytes(const char *hex, uint8_t *out, size_t out_len); +void reverse_bytes(uint8_t *data, size_t n); + +void double_sha256(const uint8_t *data, size_t len, uint8_t out[32]); +int encode_varint_hex(uint64_t value, StrBuf *sb); + +char *decode_nbits_hex(uint32_t nbits); +char *calculate_target_hex(const char *bits_hex, double difficulty_factor, const char *network); + +char *hex_add_width(const char *base_hex, int add_value); + +#endif