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