Files

482 lines
12 KiB
C
Raw Permalink Normal View History

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