482 lines
12 KiB
C
482 lines
12 KiB
C
|
|
#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;
|
||
|
|
}
|