#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; }