feat(c-miner): port miner pipeline to modular C implementation

This commit is contained in:
2026-03-30 01:07:19 +02:00
parent 9a0a170799
commit d2c118833b
18 changed files with 3299 additions and 0 deletions

495
block_builder.c Normal file
View File

@@ -0,0 +1,495 @@
#include "block_builder.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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);
}

40
block_builder.h Normal file
View File

@@ -0,0 +1,40 @@
#ifndef BLOCK_BUILDER_H
#define BLOCK_BUILDER_H
#include <stddef.h>
#include <stdint.h>
#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

248
config.c Normal file
View File

@@ -0,0 +1,248 @@
#include "config.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
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);
}

32
config.h Normal file
View File

@@ -0,0 +1,32 @@
#ifndef CONFIG_H
#define CONFIG_H
#include <stdbool.h>
#include <stdint.h>
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

401
json.c Normal file
View File

@@ -0,0 +1,401 @@
#include "json.h"
#include <stdlib.h>
#include <string.h>
#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;
}

46
json.h Normal file
View File

@@ -0,0 +1,46 @@
#ifndef JSON_H
#define JSON_H
#include <stddef.h>
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

422
launcher.c Normal file
View File

@@ -0,0 +1,422 @@
#include <poll.h>
#include <sched.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#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;
}

50
main.c Normal file
View File

@@ -0,0 +1,50 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#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);
}

165
miner.c Normal file
View File

@@ -0,0 +1,165 @@
#include "miner.h"
#include <openssl/sha.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#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;
}
}
}

30
miner.h Normal file
View File

@@ -0,0 +1,30 @@
#ifndef MINER_H
#define MINER_H
#include <stdatomic.h>
#include <stdint.h>
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

323
mining_loop.c Normal file
View File

@@ -0,0 +1,323 @@
#include "mining_loop.h"
#include <pthread.h>
#include <stdarg.h>
#include <stdatomic.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#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;
}

8
mining_loop.h Normal file
View File

@@ -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

481
rpc.c Normal file
View File

@@ -0,0 +1,481 @@
#include "rpc.h"
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#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;
}

23
rpc.h Normal file
View File

@@ -0,0 +1,23 @@
#ifndef RPC_H
#define RPC_H
#include <stddef.h>
#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

23
types.c Normal file
View File

@@ -0,0 +1,23 @@
#include "types.h"
#include <stdlib.h>
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;
}

26
types.h Normal file
View File

@@ -0,0 +1,26 @@
#ifndef TYPES_H
#define TYPES_H
#include <stddef.h>
#include <stdint.h>
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

449
utils.c Normal file
View File

@@ -0,0 +1,449 @@
#include "utils.h"
#include <ctype.h>
#include <math.h>
#include <openssl/bn.h>
#include <openssl/sha.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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;
}

37
utils.h Normal file
View File

@@ -0,0 +1,37 @@
#ifndef UTILS_H
#define UTILS_H
#include <stddef.h>
#include <stdint.h>
#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