Browse Source
Most is taken from lightningd/bitcoind and adapted. This currently exposes 5 commands: - `getchaininfo`, currently called at startup to check the network and whether we are on IBD. - `getrawblockbyheight`, which basically does the `getblockhash` + `getblock` trick. - `getfeerate` - `sendrawtransaction` - `getutxout`, used to gather infos about an output and currently used by `getfilteredblock` in `lightningd/bitcoind`.travis-debug
darosior
5 years ago
committed by
Rusty Russell
4 changed files with 767 additions and 4 deletions
@ -0,0 +1,755 @@ |
|||
#include <bitcoin/base58.h> |
|||
#include <bitcoin/block.h> |
|||
#include <bitcoin/feerate.h> |
|||
#include <bitcoin/script.h> |
|||
#include <bitcoin/shadouble.h> |
|||
#include <ccan/array_size/array_size.h> |
|||
#include <ccan/cast/cast.h> |
|||
#include <ccan/io/io.h> |
|||
#include <ccan/json_out/json_out.h> |
|||
#include <ccan/pipecmd/pipecmd.h> |
|||
#include <ccan/str/hex/hex.h> |
|||
#include <ccan/take/take.h> |
|||
#include <ccan/tal/grab_file/grab_file.h> |
|||
#include <ccan/tal/path/path.h> |
|||
#include <ccan/tal/str/str.h> |
|||
#include <common/json_helpers.h> |
|||
#include <common/memleak.h> |
|||
#include <common/utils.h> |
|||
#include <errno.h> |
|||
#include <inttypes.h> |
|||
#include <plugins/libplugin.h> |
|||
|
|||
/* Bitcoind's web server has a default of 4 threads, with queue depth 16.
|
|||
* It will *fail* rather than queue beyond that, so we must not stress it! |
|||
* |
|||
* This is how many request for each priority level we have. |
|||
*/ |
|||
#define BITCOIND_MAX_PARALLEL 4 |
|||
|
|||
enum bitcoind_prio { |
|||
BITCOIND_LOW_PRIO, |
|||
BITCOIND_HIGH_PRIO |
|||
}; |
|||
#define BITCOIND_NUM_PRIO (BITCOIND_HIGH_PRIO+1) |
|||
|
|||
struct bitcoind { |
|||
/* eg. "bitcoin-cli" */ |
|||
char *cli; |
|||
|
|||
/* -datadir arg for bitcoin-cli. */ |
|||
char *datadir; |
|||
|
|||
/* Is bitcoind synced? If not, we retry. */ |
|||
bool synced; |
|||
|
|||
/* How many high/low prio requests are we running (it's ratelimited) */ |
|||
size_t num_requests[BITCOIND_NUM_PRIO]; |
|||
|
|||
/* Pending requests (high and low prio). */ |
|||
struct list_head pending[BITCOIND_NUM_PRIO]; |
|||
|
|||
/* If non-zero, time we first hit a bitcoind error. */ |
|||
unsigned int error_count; |
|||
struct timemono first_error_time; |
|||
|
|||
/* How long to keep trying to contact bitcoind
|
|||
* before fatally exiting. */ |
|||
u64 retry_timeout; |
|||
|
|||
/* Passthrough parameters for bitcoin-cli */ |
|||
char *rpcuser, *rpcpass, *rpcconnect, *rpcport; |
|||
}; |
|||
|
|||
static struct bitcoind *bitcoind; |
|||
|
|||
struct bitcoin_cli { |
|||
struct list_node list; |
|||
int fd; |
|||
int *exitstatus; |
|||
pid_t pid; |
|||
const char **args; |
|||
struct timeabs start; |
|||
enum bitcoind_prio prio; |
|||
char *output; |
|||
size_t output_bytes; |
|||
size_t new_output; |
|||
struct command_result *(*process)(struct bitcoin_cli *); |
|||
struct command *cmd; |
|||
/* Used to stash content between multiple calls */ |
|||
void *stash; |
|||
}; |
|||
|
|||
/* Add the n'th arg to *args, incrementing n and keeping args of size n+1 */ |
|||
static void add_arg(const char ***args, const char *arg) |
|||
{ |
|||
tal_arr_expand(args, arg); |
|||
} |
|||
|
|||
static const char **gather_args(const tal_t *ctx, const char *cmd, const char **cmd_args) |
|||
{ |
|||
const char **args = tal_arr(ctx, const char *, 1); |
|||
|
|||
args[0] = bitcoind->cli ? bitcoind->cli : chainparams->cli; |
|||
if (chainparams->cli_args) |
|||
add_arg(&args, chainparams->cli_args); |
|||
if (bitcoind->datadir) |
|||
add_arg(&args, tal_fmt(args, "-datadir=%s", bitcoind->datadir)); |
|||
if (bitcoind->rpcconnect) |
|||
add_arg(&args, |
|||
tal_fmt(args, "-rpcconnect=%s", bitcoind->rpcconnect)); |
|||
if (bitcoind->rpcport) |
|||
add_arg(&args, |
|||
tal_fmt(args, "-rpcport=%s", bitcoind->rpcport)); |
|||
if (bitcoind->rpcuser) |
|||
add_arg(&args, tal_fmt(args, "-rpcuser=%s", bitcoind->rpcuser)); |
|||
if (bitcoind->rpcpass) |
|||
add_arg(&args, |
|||
tal_fmt(args, "-rpcpassword=%s", bitcoind->rpcpass)); |
|||
|
|||
add_arg(&args, cmd); |
|||
for (size_t i = 0; i < tal_count(cmd_args); i++) |
|||
add_arg(&args, cmd_args[i]); |
|||
add_arg(&args, NULL); |
|||
|
|||
return args; |
|||
} |
|||
|
|||
static struct io_plan *read_more(struct io_conn *conn, struct bitcoin_cli *bcli) |
|||
{ |
|||
bcli->output_bytes += bcli->new_output; |
|||
if (bcli->output_bytes == tal_count(bcli->output)) |
|||
tal_resize(&bcli->output, bcli->output_bytes * 2); |
|||
return io_read_partial(conn, bcli->output + bcli->output_bytes, |
|||
tal_count(bcli->output) - bcli->output_bytes, |
|||
&bcli->new_output, read_more, bcli); |
|||
} |
|||
|
|||
static struct io_plan *output_init(struct io_conn *conn, struct bitcoin_cli *bcli) |
|||
{ |
|||
bcli->output_bytes = bcli->new_output = 0; |
|||
bcli->output = tal_arr(bcli, char, 100); |
|||
return read_more(conn, bcli); |
|||
} |
|||
|
|||
static void next_bcli(enum bitcoind_prio prio); |
|||
|
|||
/* For printing: simple string of args (no secrets!) */ |
|||
static char *args_string(const tal_t *ctx, const char **args) |
|||
{ |
|||
size_t i; |
|||
char *ret = tal_strdup(ctx, args[0]); |
|||
|
|||
for (i = 1; args[i]; i++) { |
|||
ret = tal_strcat(ctx, take(ret), " "); |
|||
if (strstarts(args[i], "-rpcpassword")) { |
|||
ret = tal_strcat(ctx, take(ret), "-rpcpassword=..."); |
|||
} else if (strstarts(args[i], "-rpcuser")) { |
|||
ret = tal_strcat(ctx, take(ret), "-rpcuser=..."); |
|||
} else { |
|||
ret = tal_strcat(ctx, take(ret), args[i]); |
|||
} |
|||
} |
|||
return ret; |
|||
} |
|||
|
|||
static char *bcli_args(struct bitcoin_cli *bcli) |
|||
{ |
|||
return args_string(bcli, bcli->args); |
|||
} |
|||
|
|||
static void retry_bcli(void *cb_arg) |
|||
{ |
|||
struct bitcoin_cli *bcli = cb_arg; |
|||
list_add_tail(&bitcoind->pending[bcli->prio], &bcli->list); |
|||
next_bcli(bcli->prio); |
|||
} |
|||
|
|||
/* We allow 60 seconds of spurious errors, eg. reorg. */ |
|||
static void bcli_failure(struct bitcoin_cli *bcli, |
|||
int exitstatus) |
|||
{ |
|||
struct timerel t; |
|||
|
|||
if (!bitcoind->error_count) |
|||
bitcoind->first_error_time = time_mono(); |
|||
|
|||
t = timemono_between(time_mono(), bitcoind->first_error_time); |
|||
if (time_greater(t, time_from_sec(bitcoind->retry_timeout))) |
|||
plugin_err(bcli->cmd->plugin, |
|||
"%s exited %u (after %u other errors) '%.*s'; " |
|||
"we have been retrying command for " |
|||
"--bitcoin-retry-timeout=%"PRIu64" seconds; " |
|||
"bitcoind setup or our --bitcoin-* configs broken?", |
|||
bcli_args(bcli), |
|||
exitstatus, |
|||
bitcoind->error_count, |
|||
(int)bcli->output_bytes, |
|||
bcli->output, |
|||
bitcoind->retry_timeout); |
|||
|
|||
plugin_log(bcli->cmd->plugin, LOG_UNUSUAL, "%s exited with status %u", |
|||
bcli_args(bcli), exitstatus); |
|||
bitcoind->error_count++; |
|||
|
|||
/* Retry in 1 second (not a leak!) */ |
|||
plugin_timer(bcli->cmd->plugin, time_from_sec(1), retry_bcli, bcli); |
|||
} |
|||
|
|||
static void bcli_finished(struct io_conn *conn UNUSED, struct bitcoin_cli *bcli) |
|||
{ |
|||
int ret, status; |
|||
struct command_result *res; |
|||
enum bitcoind_prio prio = bcli->prio; |
|||
u64 msec = time_to_msec(time_between(time_now(), bcli->start)); |
|||
|
|||
/* If it took over 10 seconds, that's rather strange. */ |
|||
if (msec > 10000) |
|||
plugin_log(bcli->cmd->plugin, LOG_UNUSUAL, |
|||
"bitcoin-cli: finished %s (%"PRIu64" ms)", |
|||
bcli_args(bcli), msec); |
|||
|
|||
assert(bitcoind->num_requests[prio] > 0); |
|||
|
|||
/* FIXME: If we waited for SIGCHILD, this could never hang! */ |
|||
while ((ret = waitpid(bcli->pid, &status, 0)) < 0 && errno == EINTR); |
|||
if (ret != bcli->pid) |
|||
plugin_err(bcli->cmd->plugin, "%s %s", bcli_args(bcli), |
|||
ret == 0 ? "not exited?" : strerror(errno)); |
|||
|
|||
if (!WIFEXITED(status)) |
|||
plugin_err(bcli->cmd->plugin, "%s died with signal %i", |
|||
bcli_args(bcli), |
|||
WTERMSIG(status)); |
|||
|
|||
/* Implicit nonzero_exit_ok == false */ |
|||
if (!bcli->exitstatus) { |
|||
if (WEXITSTATUS(status) != 0) { |
|||
bcli_failure(bcli, WEXITSTATUS(status)); |
|||
bitcoind->num_requests[prio]--; |
|||
goto done; |
|||
} |
|||
} else |
|||
*bcli->exitstatus = WEXITSTATUS(status); |
|||
|
|||
if (WEXITSTATUS(status) == 0) |
|||
bitcoind->error_count = 0; |
|||
|
|||
bitcoind->num_requests[bcli->prio]--; |
|||
|
|||
res = bcli->process(bcli); |
|||
if (!res) |
|||
bcli_failure(bcli, WEXITSTATUS(status)); |
|||
else |
|||
tal_free(bcli); |
|||
|
|||
done: |
|||
next_bcli(prio); |
|||
} |
|||
|
|||
static void next_bcli(enum bitcoind_prio prio) |
|||
{ |
|||
struct bitcoin_cli *bcli; |
|||
struct io_conn *conn; |
|||
|
|||
if (bitcoind->num_requests[prio] >= BITCOIND_MAX_PARALLEL) |
|||
return; |
|||
|
|||
bcli = list_pop(&bitcoind->pending[prio], struct bitcoin_cli, list); |
|||
if (!bcli) |
|||
return; |
|||
|
|||
bcli->pid = pipecmdarr(NULL, &bcli->fd, &bcli->fd, |
|||
cast_const2(char **, bcli->args)); |
|||
if (bcli->pid < 0) |
|||
plugin_err(bcli->cmd->plugin, "%s exec failed: %s", |
|||
bcli->args[0], strerror(errno)); |
|||
|
|||
bcli->start = time_now(); |
|||
|
|||
bitcoind->num_requests[prio]++; |
|||
|
|||
conn = io_new_conn(bcli, bcli->fd, output_init, bcli); |
|||
io_set_finish(conn, bcli_finished, bcli); |
|||
} |
|||
|
|||
/* If ctx is non-NULL, and is freed before we return, we don't call process().
|
|||
* process returns false() if it's a spurious error, and we should retry. */ |
|||
static void |
|||
start_bitcoin_cli(const tal_t *ctx, |
|||
struct command *cmd, |
|||
struct command_result *(*process)(struct bitcoin_cli *), |
|||
bool nonzero_exit_ok, |
|||
enum bitcoind_prio prio, |
|||
char *method, const char **method_args, |
|||
void *stash) |
|||
{ |
|||
struct bitcoin_cli *bcli = tal(bitcoind, struct bitcoin_cli); |
|||
|
|||
bcli->process = process; |
|||
bcli->cmd = cmd; |
|||
bcli->prio = prio; |
|||
|
|||
if (nonzero_exit_ok) |
|||
bcli->exitstatus = tal(bcli, int); |
|||
else |
|||
bcli->exitstatus = NULL; |
|||
|
|||
bcli->args = gather_args(bcli, method, method_args); |
|||
bcli->stash = stash; |
|||
|
|||
list_add_tail(&bitcoind->pending[bcli->prio], &bcli->list); |
|||
next_bcli(bcli->prio); |
|||
} |
|||
|
|||
static struct command_result *process_getutxout(struct bitcoin_cli *bcli) |
|||
{ |
|||
const jsmntok_t *tokens, *valuetok, *scriptpubkeytok, *hextok; |
|||
struct json_stream *response; |
|||
bool valid; |
|||
struct bitcoin_tx_output output; |
|||
char *err; |
|||
|
|||
/* As of at least v0.15.1.0, bitcoind returns "success" but an empty
|
|||
string on a spent txout. */ |
|||
if (*bcli->exitstatus != 0 || bcli->output_bytes == 0) { |
|||
response = jsonrpc_stream_success(bcli->cmd); |
|||
json_add_null(response, "amount"); |
|||
json_add_null(response, "script"); |
|||
|
|||
return command_finished(bcli->cmd, response); |
|||
} |
|||
|
|||
tokens = json_parse_input(bcli->output, bcli->output, |
|||
bcli->output_bytes, &valid); |
|||
if (!tokens) { |
|||
err = tal_fmt(bcli, "%s: %s response", bcli_args(bcli), |
|||
valid ? "partial" : "invalid"); |
|||
return command_done_err(bcli->cmd, BCLI_ERROR, err, NULL); |
|||
} |
|||
|
|||
if (tokens[0].type != JSMN_OBJECT) { |
|||
err = tal_fmt(bcli, "%s: gave non-object (%.*s)?", |
|||
bcli_args(bcli), (int)bcli->output_bytes, |
|||
bcli->output); |
|||
return command_done_err(bcli->cmd, BCLI_ERROR, err, NULL); |
|||
} |
|||
|
|||
valuetok = json_get_member(bcli->output, tokens, "value"); |
|||
if (!valuetok) { |
|||
err = tal_fmt(bcli,"%s: had no value member (%.*s)?", |
|||
bcli_args(bcli), (int)bcli->output_bytes, |
|||
bcli->output); |
|||
return command_done_err(bcli->cmd, BCLI_ERROR, err, NULL); |
|||
} |
|||
|
|||
if (!json_to_bitcoin_amount(bcli->output, valuetok, &output.amount.satoshis)) {/* Raw: talking to bitcoind */ |
|||
err = tal_fmt(bcli, "%s: had bad value (%.*s)?", |
|||
bcli_args(bcli), (int)bcli->output_bytes, |
|||
bcli->output); |
|||
return command_done_err(bcli->cmd, BCLI_ERROR, err, NULL); |
|||
} |
|||
|
|||
scriptpubkeytok = json_get_member(bcli->output, tokens, "scriptPubKey"); |
|||
if (!scriptpubkeytok) { |
|||
err = tal_fmt(bcli, "%s: had no scriptPubKey member (%.*s)?", |
|||
bcli_args(bcli), (int)bcli->output_bytes, |
|||
bcli->output); |
|||
return command_done_err(bcli->cmd, BCLI_ERROR, err, NULL); |
|||
} |
|||
|
|||
hextok = json_get_member(bcli->output, scriptpubkeytok, "hex"); |
|||
if (!hextok) { |
|||
err = tal_fmt(bcli, "%s: had no scriptPubKey->hex member (%.*s)?", |
|||
bcli_args(bcli), (int)bcli->output_bytes, |
|||
bcli->output); |
|||
return command_done_err(bcli->cmd, BCLI_ERROR, err, NULL); |
|||
} |
|||
|
|||
output.script = tal_hexdata(bcli, bcli->output + hextok->start, |
|||
hextok->end - hextok->start); |
|||
if (!output.script) { |
|||
err = tal_fmt(bcli, "%s: scriptPubKey->hex invalid hex (%.*s)?", |
|||
bcli_args(bcli), (int)bcli->output_bytes, |
|||
bcli->output); |
|||
return command_done_err(bcli->cmd, BCLI_ERROR, err, NULL); |
|||
} |
|||
|
|||
response = jsonrpc_stream_success(bcli->cmd); |
|||
json_add_amount_sat_only(response, "amount", output.amount); |
|||
json_add_string(response, "script", |
|||
tal_hexstr(response, output.script, sizeof(output.script))); |
|||
|
|||
return command_finished(bcli->cmd, response); |
|||
} |
|||
|
|||
static struct command_result *process_getblockchaininfo(struct bitcoin_cli *bcli) |
|||
{ |
|||
const jsmntok_t *tokens, *chaintok, *headerstok, *blockstok, *ibdtok; |
|||
struct json_stream *response; |
|||
bool valid, ibd; |
|||
u32 headers, blocks; |
|||
char *err; |
|||
|
|||
tokens = json_parse_input(bcli, bcli->output, bcli->output_bytes, |
|||
&valid); |
|||
if (!tokens) { |
|||
err = tal_fmt(bcli->cmd, "%s: %s response (%.*s)", |
|||
bcli_args(bcli), valid ? "partial" : "invalid", |
|||
(int)bcli->output_bytes, bcli->output); |
|||
return command_done_err(bcli->cmd, BCLI_ERROR, err, NULL); |
|||
} |
|||
|
|||
if (tokens[0].type != JSMN_OBJECT) { |
|||
err = tal_fmt(bcli->cmd, "%s: gave non-object (%.*s)", |
|||
bcli_args(bcli), (int)bcli->output_bytes, |
|||
bcli->output); |
|||
return command_done_err(bcli->cmd, BCLI_ERROR, err, NULL); |
|||
} |
|||
|
|||
chaintok = json_get_member(bcli->output, tokens, "chain"); |
|||
if (!chaintok) { |
|||
err = tal_fmt(bcli->cmd, "%s: bad 'chain' field (%.*s)", |
|||
bcli_args(bcli), (int)bcli->output_bytes, |
|||
bcli->output); |
|||
return command_done_err(bcli->cmd, BCLI_ERROR, err, NULL); |
|||
} |
|||
|
|||
headerstok = json_get_member(bcli->output, tokens, "headers"); |
|||
if (!headerstok || !json_to_number(bcli->output, headerstok, &headers)) { |
|||
err = tal_fmt(bcli->cmd, "%s: bad 'headers' field (%.*s)", |
|||
bcli_args(bcli), (int)bcli->output_bytes, |
|||
bcli->output); |
|||
return command_done_err(bcli->cmd, BCLI_ERROR, err, NULL); |
|||
} |
|||
|
|||
blockstok = json_get_member(bcli->output, tokens, "blocks"); |
|||
if (!blockstok || !json_to_number(bcli->output, blockstok, &blocks)) { |
|||
err = tal_fmt(bcli->cmd, "%s: bad 'blocks' field (%.*s)", |
|||
bcli_args(bcli), (int)bcli->output_bytes, |
|||
bcli->output); |
|||
return command_done_err(bcli->cmd, BCLI_ERROR, err, NULL); |
|||
} |
|||
|
|||
ibdtok = json_get_member(bcli->output, tokens, "initialblockdownload"); |
|||
if (!ibdtok || !json_to_bool(bcli->output, ibdtok, &ibd)) { |
|||
err = tal_fmt(bcli->cmd, "%s: bad 'initialblockdownload' field (%.*s)", |
|||
bcli_args(bcli), (int)bcli->output_bytes, |
|||
bcli->output); |
|||
return command_done_err(bcli->cmd, BCLI_ERROR, err, NULL); |
|||
} |
|||
|
|||
response = jsonrpc_stream_success(bcli->cmd); |
|||
json_add_string(response, "chain", |
|||
json_strdup(response, bcli->output, chaintok)); |
|||
json_add_u32(response, "headercount", headers); |
|||
json_add_u32(response, "blockcount", blocks); |
|||
json_add_bool(response, "ibd", ibd); |
|||
|
|||
return command_finished(bcli->cmd, response); |
|||
} |
|||
|
|||
static struct command_result *process_estimatefee(struct bitcoin_cli *bcli) |
|||
{ |
|||
const jsmntok_t *tokens, *feeratetok = NULL; |
|||
struct json_stream *response; |
|||
bool valid; |
|||
u64 feerate; |
|||
char *err; |
|||
|
|||
if (*bcli->exitstatus != 0) |
|||
goto end; |
|||
|
|||
tokens = json_parse_input(bcli->output, bcli->output, |
|||
(int)bcli->output_bytes, &valid); |
|||
if (!tokens) { |
|||
err = tal_fmt(bcli->cmd, "%s: %s response (%.*s)", |
|||
bcli_args(bcli), valid ? "partial" : "invalid", |
|||
(int)bcli->output_bytes, bcli->output); |
|||
return command_done_err(bcli->cmd, BCLI_ERROR, err, NULL); |
|||
} |
|||
|
|||
if (tokens[0].type != JSMN_OBJECT) { |
|||
err = tal_fmt(bcli->cmd, "%s: gave non-object (%.*s)", |
|||
bcli_args(bcli), (int)bcli->output_bytes, |
|||
bcli->output); |
|||
return command_done_err(bcli->cmd, BCLI_ERROR, err, NULL); |
|||
} |
|||
|
|||
feeratetok = json_get_member(bcli->output, tokens, "feerate"); |
|||
if (feeratetok && |
|||
!json_to_bitcoin_amount(bcli->output, feeratetok, &feerate)) { |
|||
err = tal_fmt(bcli->cmd, "%s: bad 'feerate' field (%.*s)", |
|||
bcli_args(bcli), (int)bcli->output_bytes, |
|||
bcli->output); |
|||
return command_done_err(bcli->cmd, BCLI_ERROR, err, NULL); |
|||
} |
|||
|
|||
end: |
|||
response = jsonrpc_stream_success(bcli->cmd); |
|||
if (feeratetok) |
|||
json_add_u64(response, "feerate", feerate); |
|||
else |
|||
json_add_null(response, "feerate"); |
|||
|
|||
return command_finished(bcli->cmd, response); |
|||
} |
|||
|
|||
static struct command_result *process_sendrawtransaction(struct bitcoin_cli *bcli) |
|||
{ |
|||
struct json_stream *response; |
|||
|
|||
plugin_log(bcli->cmd->plugin, LOG_DBG, "sendrawtx exit %i (%s)", |
|||
*bcli->exitstatus, bcli_args(bcli)); |
|||
|
|||
response = jsonrpc_stream_success(bcli->cmd); |
|||
json_add_bool(response, "success", *bcli->exitstatus == 0); |
|||
json_add_string(response, "errmsg", |
|||
bcli->exitstatus ? tal_strndup(bcli, bcli->output, |
|||
bcli->output_bytes-1) : ""); |
|||
|
|||
return command_finished(bcli->cmd, response); |
|||
} |
|||
|
|||
struct getrawblock_stash { |
|||
const char *block_hash; |
|||
u32 block_height; |
|||
const char *block_hex; |
|||
}; |
|||
|
|||
static struct command_result *process_getrawblock(struct bitcoin_cli *bcli) |
|||
{ |
|||
struct json_stream *response; |
|||
struct getrawblock_stash *stash = bcli->stash; |
|||
|
|||
/* -1 to strip \n. */ |
|||
stash->block_hex = tal_fmt(stash, "%.*s", |
|||
(int)bcli->output_bytes-1, bcli->output); |
|||
|
|||
response = jsonrpc_stream_success(bcli->cmd); |
|||
json_add_string(response, "blockhash", stash->block_hash); |
|||
json_add_string(response, "block", stash->block_hex); |
|||
|
|||
return command_finished(bcli->cmd, response); |
|||
} |
|||
|
|||
static struct command_result * |
|||
getrawblockbyheight_notfound(struct bitcoin_cli *bcli) |
|||
{ |
|||
struct json_stream *response; |
|||
|
|||
response = jsonrpc_stream_success(bcli->cmd); |
|||
json_add_null(response, "blockhash"); |
|||
json_add_null(response, "block"); |
|||
|
|||
return command_finished(bcli->cmd, response); |
|||
} |
|||
|
|||
static struct command_result *process_getblockhash(struct bitcoin_cli *bcli) |
|||
{ |
|||
const char *err, **params; |
|||
struct getrawblock_stash *stash = bcli->stash; |
|||
|
|||
/* If it failed with error 8, give an empty response. */ |
|||
if (bcli->exitstatus && *bcli->exitstatus != 0) { |
|||
/* Other error means we have to retry. */ |
|||
if (*bcli->exitstatus != 8) |
|||
return NULL; |
|||
return getrawblockbyheight_notfound(bcli); |
|||
} |
|||
|
|||
/* `-1` to strip the newline character. */ |
|||
stash->block_hash = tal_strndup(stash, bcli->output, |
|||
bcli->output_bytes-1); |
|||
if (!stash->block_hash || strlen(stash->block_hash) != 64) { |
|||
err = tal_fmt(bcli->cmd, "%s: bad blockhash '%s'", |
|||
bcli_args(bcli), stash->block_hash); |
|||
return command_done_err(bcli->cmd, BCLI_ERROR, err, NULL); |
|||
} |
|||
|
|||
params = tal_arr(bcli->cmd, const char *, 2); |
|||
params[0] = stash->block_hash; |
|||
/* Non-verbose: raw block. */ |
|||
params[1] = "0"; |
|||
start_bitcoin_cli(NULL, bcli->cmd, process_getrawblock, false, |
|||
BITCOIND_HIGH_PRIO, "getblock", params, stash); |
|||
|
|||
return command_still_pending(bcli->cmd); |
|||
} |
|||
|
|||
/* Get a raw block given its height.
|
|||
* Calls `getblockhash` then `getblock` to retrieve it from bitcoin_cli. |
|||
* Will return early with null fields if block isn't known (yet). |
|||
*/ |
|||
static struct command_result *getrawblockbyheight(struct command *cmd, |
|||
const char *buf, |
|||
const jsmntok_t *toks) |
|||
{ |
|||
struct getrawblock_stash *stash; |
|||
u32 *height; |
|||
const char **params; |
|||
|
|||
/* bitcoin-cli wants a string. */ |
|||
if (!param(cmd, buf, toks, |
|||
p_req("height", param_number, &height), |
|||
NULL)) |
|||
return command_param_failed(); |
|||
|
|||
stash = tal(cmd, struct getrawblock_stash); |
|||
stash->block_height = *height; |
|||
|
|||
params = tal_arr(cmd, const char *, 1); |
|||
params[0] = tal_fmt(params, "%u", *height); |
|||
start_bitcoin_cli(NULL, cmd, process_getblockhash, true, |
|||
BITCOIND_LOW_PRIO, "getblockhash", params, stash); |
|||
|
|||
return command_still_pending(cmd); |
|||
} |
|||
|
|||
/* Get infos about the block chain.
|
|||
* Calls `getblockchaininfo` and returns headers count, blocks count, |
|||
* the chain id, and whether this is initialblockdownload. |
|||
*/ |
|||
static struct command_result *getchaininfo(struct command *cmd, |
|||
const char *buf UNUSED, |
|||
const jsmntok_t *toks UNUSED) |
|||
{ |
|||
if (!param(cmd, buf, toks, NULL)) |
|||
return command_param_failed(); |
|||
|
|||
start_bitcoin_cli(NULL, cmd, process_getblockchaininfo, false, |
|||
BITCOIND_HIGH_PRIO, "getblockchaininfo", NULL, NULL); |
|||
|
|||
return command_still_pending(cmd); |
|||
} |
|||
|
|||
/* Get current feerate.
|
|||
* Calls `estimatesmartfee` and returns the feerate as btc/k*VBYTE*. |
|||
*/ |
|||
static struct command_result *getfeerate(struct command *cmd, |
|||
const char *buf UNUSED, |
|||
const jsmntok_t *toks UNUSED) |
|||
{ |
|||
u32 *blocks; |
|||
const char **params = tal_arr(cmd, const char *, 2); |
|||
|
|||
if (!param(cmd, buf, toks, |
|||
p_req("blocks", param_number, &blocks), |
|||
p_req("mode", param_string, ¶ms[1]), |
|||
NULL)) |
|||
return command_param_failed(); |
|||
|
|||
params[0] = tal_fmt(params, "%u", *blocks); |
|||
start_bitcoin_cli(NULL, cmd, process_estimatefee, true, |
|||
BITCOIND_LOW_PRIO, "estimatesmartfee", params, NULL); |
|||
|
|||
return command_still_pending(cmd); |
|||
} |
|||
|
|||
/* Send a transaction to the Bitcoin network.
|
|||
* Calls `sendrawtransaction` using the first parameter as the raw tx. |
|||
*/ |
|||
static struct command_result *sendrawtransaction(struct command *cmd, |
|||
const char *buf, |
|||
const jsmntok_t *toks) |
|||
{ |
|||
const char **params = tal_arr(cmd, const char *, 1); |
|||
|
|||
/* bitcoin-cli wants strings. */ |
|||
if (!param(cmd, buf, toks, |
|||
p_req("tx", param_string, ¶ms[0]), |
|||
NULL)) |
|||
return command_param_failed(); |
|||
|
|||
start_bitcoin_cli(NULL, cmd, process_sendrawtransaction, true, |
|||
BITCOIND_HIGH_PRIO, "sendrawtransaction", params, NULL); |
|||
|
|||
return command_still_pending(cmd); |
|||
} |
|||
|
|||
static struct command_result *getutxout(struct command *cmd, |
|||
const char *buf, |
|||
const jsmntok_t *toks) |
|||
{ |
|||
const char **params = tal_arr(cmd, const char *, 2); |
|||
|
|||
/* bitcoin-cli wants strings. */ |
|||
if (!param(cmd, buf, toks, |
|||
p_req("txid", param_string, ¶ms[0]), |
|||
p_req("vout", param_string, ¶ms[1]), |
|||
NULL)) |
|||
return command_param_failed(); |
|||
|
|||
start_bitcoin_cli(NULL, cmd, process_getutxout, true, |
|||
BITCOIND_HIGH_PRIO, "gettxout", params, NULL); |
|||
|
|||
return command_still_pending(cmd); |
|||
} |
|||
|
|||
/* Initialize the global context when handshake is done. */ |
|||
static void init(struct plugin *p UNUSED, const char *buffer UNUSED, |
|||
const jsmntok_t *config UNUSED) |
|||
{ |
|||
bitcoind = tal(NULL, struct bitcoind); |
|||
|
|||
bitcoind->cli = NULL; |
|||
bitcoind->datadir = NULL; |
|||
for (size_t i = 0; i < BITCOIND_NUM_PRIO; i++) { |
|||
bitcoind->num_requests[i] = 0; |
|||
list_head_init(&bitcoind->pending[i]); |
|||
} |
|||
bitcoind->error_count = 0; |
|||
bitcoind->retry_timeout = 60; |
|||
bitcoind->rpcuser = NULL; |
|||
bitcoind->rpcpass = NULL; |
|||
bitcoind->rpcconnect = NULL; |
|||
bitcoind->rpcport = NULL; |
|||
} |
|||
|
|||
static const struct plugin_command commands[] = { |
|||
{ |
|||
"getrawblockbyheight", |
|||
"bitcoin", |
|||
"Get the bitcoin block at a given height", |
|||
"", |
|||
getrawblockbyheight |
|||
}, |
|||
{ |
|||
"getchaininfo", |
|||
"bitcoin", |
|||
"Get the chain id, the header count, the block count," |
|||
" and whether this is IBD.", |
|||
"", |
|||
getchaininfo |
|||
}, |
|||
{ |
|||
"getfeerate", |
|||
"bitcoin", |
|||
"Get the Bitcoin feerate in btc/kilo-vbyte.", |
|||
"", |
|||
getfeerate |
|||
}, |
|||
{ |
|||
"sendrawtransaction", |
|||
"bitcoin", |
|||
"Send a raw transaction to the Bitcoin network.", |
|||
"", |
|||
sendrawtransaction |
|||
}, |
|||
{ |
|||
"getutxout", |
|||
"bitcoin", |
|||
"Get informations about an output, identified by a {txid} an a {vout}", |
|||
"", |
|||
getutxout |
|||
}, |
|||
}; |
|||
|
|||
int main(int argc, char *argv[]) |
|||
{ |
|||
setup_locale(); |
|||
|
|||
/* FIXME: handle bitcoind options at init */ |
|||
plugin_main(argv, init, PLUGIN_STATIC, commands, ARRAY_SIZE(commands), |
|||
NULL, 0, NULL, 0, NULL); |
|||
} |
Loading…
Reference in new issue