Some checks failed
Coverage (Nightly) / Build with Coverage (push) Has been cancelled
Coverage (Nightly) / Test (postgres) (push) Has been cancelled
Coverage (Nightly) / Test (sqlite) (push) Has been cancelled
Coverage (Nightly) / Generate Coverage Report (push) Has been cancelled
Repro Build Nightly / Ubuntu repro build: focal (push) Has been cancelled
Repro Build Nightly / Ubuntu repro build: jammy (push) Has been cancelled
Repro Build Nightly / Ubuntu repro build: noble (push) Has been cancelled
Python API Docs (Nightly) / Generate Python API Documentation (push) Has been cancelled
Documentation (Nightly) / Generate Project Documentation (push) Has been cancelled
Publish Documentation Site / Generate Coverage Reports (push) Has been cancelled
Publish Documentation Site / Generate Python API Documentation (push) Has been cancelled
Publish Documentation Site / Generate Project Documentation (push) Has been cancelled
Publish Documentation Site / Deploy to GitHub Pages (push) Has been cancelled
943 lines
28 KiB
C
943 lines
28 KiB
C
#include "config.h"
|
|
#include <ccan/array_size/array_size.h>
|
|
#include <ccan/cast/cast.h>
|
|
#include <ccan/pipecmd/pipecmd.h>
|
|
#include <ccan/read_write_all/read_write_all.h>
|
|
#include <ccan/tal/grab_file/grab_file.h>
|
|
#include <ccan/tal/str/str.h>
|
|
#include <common/json_param.h>
|
|
#include <common/json_stream.h>
|
|
#include <common/memleak.h>
|
|
#include <errno.h>
|
|
#include <inttypes.h>
|
|
#include <plugins/libplugin.h>
|
|
#include <unistd.h>
|
|
|
|
/* Bitcoin Core RPC error code for duplicate transaction */
|
|
#define RPC_TRANSACTION_ALREADY_IN_CHAIN -27
|
|
|
|
/* Hex-encoded SHA256 block hash length (32 bytes = 64 hex chars) */
|
|
#define BLOCK_HASH_HEX_LEN 64
|
|
|
|
/* Bitcoin Core version 23.0.0 introduced getblockfrompeer RPC */
|
|
#define BITCOIND_VERSION_GETBLOCKFROMPEER 230000
|
|
|
|
struct bitcoind {
|
|
/* eg. "palladium-cli" */
|
|
char *cli;
|
|
|
|
/* -datadir arg for palladium-cli. */
|
|
char *datadir;
|
|
|
|
/* bitcoind's version, used for compatibility checks. */
|
|
u32 version;
|
|
|
|
/* How long to keep trying to contact bitcoind
|
|
* before fatally exiting. */
|
|
u64 retry_timeout;
|
|
|
|
/* Passthrough parameters for palladium-cli */
|
|
char *rpcuser, *rpcpass, *rpcconnect, *rpcport;
|
|
u64 rpcclienttimeout;
|
|
|
|
/* Whether we fake fees (regtest) */
|
|
bool fake_fees;
|
|
|
|
/* Override in case we're developer mode for testing*/
|
|
bool dev_no_fake_fees;
|
|
|
|
/* Override initialblockdownload (using canned blocks sets this) */
|
|
bool dev_ignore_ibd;
|
|
};
|
|
|
|
static struct bitcoind *bitcoind;
|
|
|
|
/* Result of a synchronous palladium-cli call */
|
|
struct bcli_result {
|
|
char *output;
|
|
size_t output_len;
|
|
int exitstatus;
|
|
/* Command args string for error messages */
|
|
const char *args;
|
|
};
|
|
|
|
/* 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 TAKES)
|
|
{
|
|
if (taken(arg))
|
|
tal_steal(*args, arg);
|
|
tal_arr_expand(args, arg);
|
|
}
|
|
|
|
/* If stdinargs is non-NULL, that is where we put additional args */
|
|
static const char **gather_argsv(const tal_t *ctx, const char ***stdinargs, const char *cmd, va_list ap)
|
|
{
|
|
const char **args = tal_arr(ctx, const char *, 1);
|
|
const char *arg;
|
|
|
|
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->rpcclienttimeout) {
|
|
/* Use the maximum value of rpcclienttimeout and retry_timeout to avoid
|
|
the bitcoind backend hanging for too long. */
|
|
if (bitcoind->retry_timeout &&
|
|
bitcoind->retry_timeout > bitcoind->rpcclienttimeout)
|
|
bitcoind->rpcclienttimeout = bitcoind->retry_timeout;
|
|
|
|
add_arg(&args,
|
|
tal_fmt(args, "-rpcclienttimeout=%"PRIu64, bitcoind->rpcclienttimeout));
|
|
}
|
|
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)
|
|
// Always pipe the rpcpassword via stdin. Do not pass it using an
|
|
// `-rpcpassword` argument - secrets in arguments can leak when listing
|
|
// system processes.
|
|
add_arg(&args, "-stdinrpcpass");
|
|
/* To avoid giant command lines, we use -stdin (avail since bitcoin 0.13) */
|
|
if (stdinargs)
|
|
add_arg(&args, "-stdin");
|
|
|
|
add_arg(&args, cmd);
|
|
while ((arg = va_arg(ap, char *)) != NULL) {
|
|
if (stdinargs)
|
|
add_arg(stdinargs, arg);
|
|
else
|
|
add_arg(&args, arg);
|
|
}
|
|
add_arg(&args, NULL);
|
|
|
|
return args;
|
|
}
|
|
|
|
static LAST_ARG_NULL const char **
|
|
gather_args(const tal_t *ctx, const char ***stdinargs, const char *cmd, ...)
|
|
{
|
|
va_list ap;
|
|
const char **ret;
|
|
|
|
va_start(ap, cmd);
|
|
ret = gather_argsv(ctx, stdinargs, cmd, ap);
|
|
va_end(ap);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* For printing: simple string of args (no secrets!) */
|
|
static char *args_string(const tal_t *ctx, const char **args, const char **stdinargs)
|
|
{
|
|
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]);
|
|
}
|
|
}
|
|
for (i = 0; i < tal_count(stdinargs); i++) {
|
|
ret = tal_strcat(ctx, take(ret), " ");
|
|
ret = tal_strcat(ctx, take(ret), stdinargs[i]);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* Execute palladium-cli with pre-built command and optional stdin args.
|
|
* Returns result with output and exit status. */
|
|
static struct bcli_result *
|
|
execute_bitcoin_cli(const tal_t *ctx,
|
|
struct plugin *plugin,
|
|
const char **cmd,
|
|
const char **stdinargs)
|
|
{
|
|
int in, from, status;
|
|
pid_t child;
|
|
struct bcli_result *res;
|
|
|
|
child = pipecmdarr(&in, &from, &from, cast_const2(char **, cmd));
|
|
if (child < 0)
|
|
plugin_err(plugin, "%s exec failed: %s", cmd[0], strerror(errno));
|
|
|
|
/* Send rpcpass via stdin if configured */
|
|
if (bitcoind->rpcpass) {
|
|
write_all(in, bitcoind->rpcpass, strlen(bitcoind->rpcpass));
|
|
write_all(in, "\n", 1);
|
|
}
|
|
/* Send any additional stdin args */
|
|
if (stdinargs) {
|
|
for (size_t i = 0; i < tal_count(stdinargs); i++) {
|
|
write_all(in, stdinargs[i], strlen(stdinargs[i]));
|
|
write_all(in, "\n", 1);
|
|
}
|
|
}
|
|
close(in);
|
|
|
|
/* Read all output until EOF */
|
|
res = tal(ctx, struct bcli_result);
|
|
res->output = grab_fd_str(res, from);
|
|
res->output_len = strlen(res->output);
|
|
res->args = args_string(res, cmd, stdinargs);
|
|
close(from);
|
|
|
|
/* Wait for child to exit */
|
|
while (waitpid(child, &status, 0) < 0) {
|
|
if (errno == EINTR)
|
|
continue;
|
|
plugin_err(plugin, "waitpid(%s) failed: %s",
|
|
res->args, strerror(errno));
|
|
}
|
|
|
|
if (!WIFEXITED(status))
|
|
plugin_err(plugin, "%s died with signal %i",
|
|
res->args, WTERMSIG(status));
|
|
|
|
res->exitstatus = WEXITSTATUS(status);
|
|
|
|
return res;
|
|
}
|
|
|
|
/* Synchronous execution of palladium-cli.
|
|
* Returns result with output and exit status. */
|
|
static struct bcli_result *
|
|
run_bitcoin_cliv(const tal_t *ctx,
|
|
struct plugin *plugin,
|
|
const char *method,
|
|
va_list ap)
|
|
{
|
|
const char **stdinargs;
|
|
const char **cmd;
|
|
|
|
stdinargs = tal_arr(ctx, const char *, 0);
|
|
cmd = gather_argsv(ctx, &stdinargs, method, ap);
|
|
|
|
return execute_bitcoin_cli(ctx, plugin, cmd, stdinargs);
|
|
}
|
|
|
|
static LAST_ARG_NULL struct bcli_result *
|
|
run_bitcoin_cli(const tal_t *ctx,
|
|
struct plugin *plugin,
|
|
const char *method, ...)
|
|
{
|
|
va_list ap;
|
|
struct bcli_result *res;
|
|
|
|
va_start(ap, method);
|
|
res = run_bitcoin_cliv(ctx, plugin, method, ap);
|
|
va_end(ap);
|
|
|
|
return res;
|
|
}
|
|
|
|
static void strip_trailing_whitespace(char *str, size_t len)
|
|
{
|
|
size_t stripped_len = len;
|
|
while (stripped_len > 0 && cisspace(str[stripped_len-1]))
|
|
stripped_len--;
|
|
|
|
str[stripped_len] = 0x00;
|
|
}
|
|
|
|
static struct command_result *command_err(struct command *cmd,
|
|
struct bcli_result *res,
|
|
const char *errmsg)
|
|
{
|
|
char *err = tal_fmt(cmd, "%s: %s (%.*s)",
|
|
res->args, errmsg, (int)res->output_len, res->output);
|
|
return command_done_err(cmd, BCLI_ERROR, err, NULL);
|
|
}
|
|
|
|
/* Don't use this in general: it's better to omit fields. */
|
|
static void json_add_null(struct json_stream *stream, const char *fieldname)
|
|
{
|
|
json_add_primitive(stream, fieldname, "null");
|
|
}
|
|
|
|
struct estimatefee_params {
|
|
u32 blocks;
|
|
const char *style;
|
|
};
|
|
|
|
static const struct estimatefee_params estimatefee_params[] = {
|
|
{ 2, "CONSERVATIVE" },
|
|
{ 6, "ECONOMICAL" },
|
|
{ 12, "ECONOMICAL" },
|
|
{ 100, "ECONOMICAL" },
|
|
};
|
|
|
|
static struct command_result *
|
|
estimatefees_null_response(struct command *cmd)
|
|
{
|
|
struct json_stream *response = jsonrpc_stream_success(cmd);
|
|
|
|
/* We give a floor, which is the standard minimum */
|
|
json_array_start(response, "feerates");
|
|
json_array_end(response);
|
|
json_add_u32(response, "feerate_floor", 1000);
|
|
|
|
return command_finished(cmd, response);
|
|
}
|
|
|
|
static struct command_result *
|
|
getrawblockbyheight_notfound(struct command *cmd)
|
|
{
|
|
struct json_stream *response;
|
|
|
|
response = jsonrpc_stream_success(cmd);
|
|
json_add_null(response, "blockhash");
|
|
json_add_null(response, "block");
|
|
|
|
return command_finished(cmd, response);
|
|
}
|
|
|
|
/* Get peers that support NODE_NETWORK (full nodes).
|
|
* Returns array of peer ids, or empty array if none found. */
|
|
static int *get_fullnode_peers(const tal_t *ctx, struct command *cmd)
|
|
{
|
|
struct bcli_result *res;
|
|
const jsmntok_t *t, *toks;
|
|
int *peers = tal_arr(ctx, int, 0);
|
|
size_t i;
|
|
|
|
res = run_bitcoin_cli(cmd, cmd->plugin, "getpeerinfo", NULL);
|
|
if (res->exitstatus != 0)
|
|
return peers;
|
|
|
|
toks = json_parse_simple(res->output, res->output, res->output_len);
|
|
if (!toks)
|
|
return peers;
|
|
|
|
json_for_each_arr(i, t, toks) {
|
|
int id;
|
|
u8 *services;
|
|
|
|
if (json_scan(tmpctx, res->output, t, "{id:%,services:%}",
|
|
JSON_SCAN(json_to_int, &id),
|
|
JSON_SCAN_TAL(tmpctx, json_tok_bin_from_hex, &services)) == NULL) {
|
|
/* From bitcoin source:
|
|
* NODE_NETWORK means that the node is capable of serving the complete block chain.
|
|
* It is currently set by all Bitcoin Core non pruned nodes, and is unset by SPV
|
|
* clients or other light clients.
|
|
* NODE_NETWORK = (1 << 0)
|
|
*/
|
|
if (tal_count(services) > 0 && (services[tal_count(services)-1] & (1 << 0)))
|
|
tal_arr_expand(&peers, id);
|
|
}
|
|
}
|
|
|
|
return peers;
|
|
}
|
|
|
|
/* 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 bcli_result *res;
|
|
struct json_stream *response;
|
|
const char *block_hash;
|
|
u32 *height;
|
|
struct timemono first_error_time;
|
|
bool first_error = true;
|
|
int *peers = NULL;
|
|
|
|
if (!param(cmd, buf, toks,
|
|
p_req("height", param_number, &height),
|
|
NULL))
|
|
return command_param_failed();
|
|
|
|
res = run_bitcoin_cli(cmd, cmd->plugin, "getblockhash",
|
|
tal_fmt(tmpctx, "%u", *height), NULL);
|
|
|
|
if (res->exitstatus != 0) {
|
|
return getrawblockbyheight_notfound(cmd);
|
|
}
|
|
|
|
strip_trailing_whitespace(res->output, res->output_len);
|
|
if (strlen(res->output) != BLOCK_HASH_HEX_LEN)
|
|
return command_err(cmd, res, "bad JSON: bad blockhash");
|
|
|
|
block_hash = tal_strdup(cmd, res->output);
|
|
|
|
for (;;) {
|
|
res = run_bitcoin_cli(cmd, cmd->plugin, "getblock",
|
|
block_hash, "0", NULL);
|
|
|
|
if (res->exitstatus == 0) {
|
|
strip_trailing_whitespace(res->output, res->output_len);
|
|
response = jsonrpc_stream_success(cmd);
|
|
json_add_string(response, "blockhash", block_hash);
|
|
json_add_string(response, "block", res->output);
|
|
return command_finished(cmd, response);
|
|
}
|
|
|
|
plugin_log(cmd->plugin, LOG_DBG,
|
|
"failed to fetch block %s from the bitcoin backend (maybe pruned).",
|
|
block_hash);
|
|
|
|
if (first_error) {
|
|
first_error_time = time_mono();
|
|
first_error = false;
|
|
}
|
|
|
|
struct timerel elapsed = timemono_between(time_mono(), first_error_time);
|
|
if (time_greater(elapsed, time_from_sec(bitcoind->retry_timeout))) {
|
|
return command_done_err(cmd, BCLI_ERROR,
|
|
tal_fmt(cmd, "getblock %s timed out after %"PRIu64" seconds",
|
|
block_hash, bitcoind->retry_timeout), NULL);
|
|
}
|
|
|
|
/* Try fetching from peers if bitcoind >= 23.0.0 */
|
|
if (bitcoind->version >= BITCOIND_VERSION_GETBLOCKFROMPEER) {
|
|
if (!peers)
|
|
peers = get_fullnode_peers(cmd, cmd);
|
|
|
|
if (tal_count(peers) > 0) {
|
|
int peer = peers[tal_count(peers) - 1];
|
|
tal_resize(&peers, tal_count(peers) - 1);
|
|
|
|
res = run_bitcoin_cli(cmd, cmd->plugin,
|
|
"getblockfrompeer",
|
|
block_hash,
|
|
tal_fmt(tmpctx, "%i", peer),
|
|
NULL);
|
|
|
|
if (res->exitstatus != 0) {
|
|
/* We still continue with the execution if we cannot fetch the
|
|
* block from peer */
|
|
plugin_log(cmd->plugin, LOG_DBG,
|
|
"failed to fetch block %s from peer %i, skip.",
|
|
block_hash, peer);
|
|
} else {
|
|
plugin_log(cmd->plugin, LOG_DBG,
|
|
"try to fetch block %s from peer %i.",
|
|
block_hash, peer);
|
|
}
|
|
}
|
|
|
|
if (tal_count(peers) == 0) {
|
|
plugin_log(cmd->plugin, LOG_DBG,
|
|
"asked all known peers about block %s, retry",
|
|
block_hash);
|
|
peers = tal_free(peers);
|
|
}
|
|
}
|
|
|
|
sleep(1);
|
|
}
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
/* FIXME(vincenzopalazzo): Inside the JSON request,
|
|
* we have the current height known from Core Lightning. Therefore,
|
|
* we can attempt to prevent a crash if the 'getchaininfo' function returns
|
|
* a lower height than the one we already know, by waiting for a short period.
|
|
* However, I currently don't have a better idea on how to handle this situation. */
|
|
u32 *height UNUSED;
|
|
struct bcli_result *res;
|
|
const jsmntok_t *tokens;
|
|
struct json_stream *response;
|
|
bool ibd;
|
|
u32 headers, blocks;
|
|
const char *chain, *err;
|
|
|
|
if (!param(cmd, buf, toks,
|
|
p_opt("last_height", param_number, &height),
|
|
NULL))
|
|
return command_param_failed();
|
|
|
|
res = run_bitcoin_cli(cmd, cmd->plugin, "getblockchaininfo", NULL);
|
|
if (res->exitstatus != 0)
|
|
return command_err(cmd, res, "command failed");
|
|
|
|
tokens = json_parse_simple(res->output, res->output, res->output_len);
|
|
if (!tokens)
|
|
return command_err(cmd, res, "bad JSON: cannot parse");
|
|
|
|
err = json_scan(tmpctx, res->output, tokens,
|
|
"{chain:%,headers:%,blocks:%,initialblockdownload:%}",
|
|
JSON_SCAN_TAL(tmpctx, json_strdup, &chain),
|
|
JSON_SCAN(json_to_number, &headers),
|
|
JSON_SCAN(json_to_number, &blocks),
|
|
JSON_SCAN(json_to_bool, &ibd));
|
|
if (err)
|
|
return command_err(cmd, res, tal_fmt(tmpctx, "bad JSON: %s", err));
|
|
|
|
if (bitcoind->dev_ignore_ibd)
|
|
ibd = false;
|
|
|
|
response = jsonrpc_stream_success(cmd);
|
|
json_add_string(response, "chain", chain);
|
|
json_add_u32(response, "headercount", headers);
|
|
json_add_u32(response, "blockcount", blocks);
|
|
json_add_bool(response, "ibd", ibd);
|
|
|
|
return command_finished(cmd, response);
|
|
}
|
|
|
|
/* Add a feerate, but don't publish one that bitcoind won't accept. */
|
|
static void json_add_feerate(struct json_stream *result, const char *fieldname,
|
|
struct command *cmd,
|
|
u64 perkb_floor,
|
|
u64 value)
|
|
{
|
|
/* Anthony Towns reported signet had a 900kbtc fee block, and then
|
|
* CLN got upset scanning feerate. It expects a u32. */
|
|
if (value > 0xFFFFFFFF) {
|
|
plugin_log(cmd->plugin, LOG_UNUSUAL,
|
|
"Feerate %"PRIu64" is ridiculous: trimming to 32 bits",
|
|
value);
|
|
value = 0xFFFFFFFF;
|
|
}
|
|
/* 0 is special, it means "unknown" */
|
|
if (value && value < perkb_floor) {
|
|
plugin_log(cmd->plugin, LOG_DBG,
|
|
"Feerate %s raised from %"PRIu64
|
|
" perkb to floor of %"PRIu64,
|
|
fieldname, value, perkb_floor);
|
|
json_add_u64(result, fieldname, perkb_floor);
|
|
} else {
|
|
json_add_u64(result, fieldname, value);
|
|
}
|
|
}
|
|
|
|
/* Get the feerate floor from getmempoolinfo.
|
|
* Returns NULL on success (floor stored in *perkb_floor), or error response. */
|
|
static struct command_result *get_feerate_floor(struct command *cmd,
|
|
u64 *perkb_floor)
|
|
{
|
|
struct bcli_result *res;
|
|
const jsmntok_t *tokens;
|
|
const char *err;
|
|
u64 mempoolfee, relayfee;
|
|
|
|
res = run_bitcoin_cli(cmd, cmd->plugin, "getmempoolinfo", NULL);
|
|
if (res->exitstatus != 0)
|
|
return estimatefees_null_response(cmd);
|
|
|
|
tokens = json_parse_simple(res->output, res->output, res->output_len);
|
|
if (!tokens)
|
|
return command_err(cmd, res, "bad JSON: cannot parse");
|
|
|
|
err = json_scan(tmpctx, res->output, tokens,
|
|
"{mempoolminfee:%,minrelaytxfee:%}",
|
|
JSON_SCAN(json_to_bitcoin_amount, &mempoolfee),
|
|
JSON_SCAN(json_to_bitcoin_amount, &relayfee));
|
|
if (err)
|
|
return command_err(cmd, res, tal_fmt(tmpctx, "bad JSON: %s", err));
|
|
|
|
*perkb_floor = max_u64(mempoolfee, relayfee);
|
|
return NULL;
|
|
}
|
|
|
|
/* Get a single feerate from estimatesmartfee.
|
|
* Returns NULL on success (feerate stored in *perkb), or error response. */
|
|
static struct command_result *get_feerate(struct command *cmd,
|
|
u32 blocks,
|
|
const char *style,
|
|
u64 *perkb)
|
|
{
|
|
struct bcli_result *res;
|
|
const jsmntok_t *tokens;
|
|
|
|
res = run_bitcoin_cli(cmd, cmd->plugin, "estimatesmartfee",
|
|
tal_fmt(tmpctx, "%u", blocks), style, NULL);
|
|
|
|
if (res->exitstatus != 0)
|
|
return estimatefees_null_response(cmd);
|
|
|
|
tokens = json_parse_simple(res->output, res->output, res->output_len);
|
|
if (!tokens)
|
|
return command_err(cmd, res, "bad JSON: cannot parse");
|
|
|
|
if (json_scan(tmpctx, res->output, tokens, "{feerate:%}",
|
|
JSON_SCAN(json_to_bitcoin_amount, perkb)) != NULL) {
|
|
/* Paranoia: if it had a feerate, but was malformed: */
|
|
if (json_get_member(res->output, tokens, "feerate"))
|
|
return command_err(cmd, res, "bad JSON: cannot scan");
|
|
/* Regtest fee estimation is generally awful: Fake it at min. */
|
|
if (bitcoind->fake_fees)
|
|
*perkb = 1000;
|
|
else
|
|
/* We return null if estimation failed, and palladium-cli will
|
|
* exit with 0 but no feerate field on failure. */
|
|
return estimatefees_null_response(cmd);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Get the current feerates. We use an urgent feerate for unilateral_close and max,
|
|
* a slightly less urgent feerate for htlc_resolution and penalty transactions,
|
|
* a slow feerate for min, and a normal one for all others.
|
|
*/
|
|
static struct command_result *estimatefees(struct command *cmd,
|
|
const char *buf UNUSED,
|
|
const jsmntok_t *toks UNUSED)
|
|
{
|
|
struct command_result *err;
|
|
u64 perkb_floor = 0;
|
|
u64 perkb[ARRAY_SIZE(estimatefee_params)];
|
|
struct json_stream *response;
|
|
|
|
if (!param(cmd, buf, toks, NULL))
|
|
return command_param_failed();
|
|
|
|
err = get_feerate_floor(cmd, &perkb_floor);
|
|
if (err)
|
|
return err;
|
|
|
|
for (size_t i = 0; i < ARRAY_SIZE(estimatefee_params); i++) {
|
|
err = get_feerate(cmd, estimatefee_params[i].blocks,
|
|
estimatefee_params[i].style, &perkb[i]);
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
response = jsonrpc_stream_success(cmd);
|
|
json_array_start(response, "feerates");
|
|
for (size_t i = 0; i < ARRAY_SIZE(perkb); i++) {
|
|
if (!perkb[i])
|
|
continue;
|
|
json_object_start(response, NULL);
|
|
json_add_u32(response, "blocks", estimatefee_params[i].blocks);
|
|
json_add_feerate(response, "feerate", cmd, perkb_floor, perkb[i]);
|
|
json_object_end(response);
|
|
}
|
|
json_array_end(response);
|
|
json_add_u64(response, "feerate_floor", perkb_floor);
|
|
return command_finished(cmd, response);
|
|
}
|
|
|
|
/* 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 *tx, *highfeesarg;
|
|
bool *allowhighfees;
|
|
struct bcli_result *res;
|
|
struct json_stream *response;
|
|
|
|
/* palladium-cli wants strings. */
|
|
if (!param(cmd, buf, toks,
|
|
p_req("tx", param_string, &tx),
|
|
p_req("allowhighfees", param_bool, &allowhighfees),
|
|
NULL))
|
|
return command_param_failed();
|
|
|
|
if (*allowhighfees) {
|
|
highfeesarg = "0";
|
|
} else
|
|
highfeesarg = NULL;
|
|
|
|
res = run_bitcoin_cli(cmd, cmd->plugin,
|
|
"sendrawtransaction", tx, highfeesarg, NULL);
|
|
|
|
/* This is useful for functional tests. */
|
|
plugin_log(cmd->plugin, LOG_DBG,
|
|
"sendrawtx exit %i (%s) %.*s",
|
|
res->exitstatus, res->args,
|
|
res->exitstatus ? (int)res->output_len : 0,
|
|
res->output);
|
|
|
|
response = jsonrpc_stream_success(cmd);
|
|
json_add_bool(response, "success",
|
|
res->exitstatus == 0 ||
|
|
res->exitstatus == RPC_TRANSACTION_ALREADY_IN_CHAIN);
|
|
json_add_string(response, "errmsg",
|
|
res->exitstatus ?
|
|
tal_strndup(cmd, res->output, res->output_len)
|
|
: "");
|
|
|
|
return command_finished(cmd, response);
|
|
}
|
|
|
|
static struct command_result *getutxout(struct command *cmd,
|
|
const char *buf,
|
|
const jsmntok_t *toks)
|
|
{
|
|
const char *txid, *vout;
|
|
struct bcli_result *res;
|
|
const jsmntok_t *tokens;
|
|
struct json_stream *response;
|
|
struct bitcoin_tx_output output;
|
|
const char *err;
|
|
|
|
/* palladium-cli wants strings. */
|
|
if (!param(cmd, buf, toks,
|
|
p_req("txid", param_string, &txid),
|
|
p_req("vout", param_string, &vout),
|
|
NULL))
|
|
return command_param_failed();
|
|
|
|
res = run_bitcoin_cli(cmd, cmd->plugin, "gettxout", txid, vout, NULL);
|
|
|
|
/* As of at least v0.15.1.0, bitcoind returns "success" but an empty
|
|
string on a spent txout. */
|
|
if (res->exitstatus != 0 || res->output_len == 0) {
|
|
response = jsonrpc_stream_success(cmd);
|
|
json_add_null(response, "amount");
|
|
json_add_null(response, "script");
|
|
return command_finished(cmd, response);
|
|
}
|
|
|
|
tokens = json_parse_simple(res->output, res->output, res->output_len);
|
|
if (!tokens)
|
|
return command_err(cmd, res, "bad JSON: cannot parse");
|
|
|
|
err = json_scan(tmpctx, res->output, tokens,
|
|
"{value:%,scriptPubKey:{hex:%}}",
|
|
JSON_SCAN(json_to_bitcoin_amount,
|
|
&output.amount.satoshis), /* Raw: bitcoind */
|
|
JSON_SCAN_TAL(cmd, json_tok_bin_from_hex,
|
|
&output.script));
|
|
if (err)
|
|
return command_err(cmd, res, tal_fmt(tmpctx, "bad JSON: %s", err));
|
|
|
|
response = jsonrpc_stream_success(cmd);
|
|
json_add_sats(response, "amount", output.amount);
|
|
json_add_string(response, "script", tal_hex(response, output.script));
|
|
|
|
return command_finished(cmd, response);
|
|
}
|
|
|
|
static void bitcoind_failure(struct plugin *p, const char *error_message)
|
|
{
|
|
const char **cmd = gather_args(bitcoind, NULL, "echo", NULL);
|
|
plugin_err(p, "\n%s\n\n"
|
|
"Make sure you have palladiumd running and that palladium-cli"
|
|
" is able to connect to palladiumd.\n\n"
|
|
"You can verify that your Palladium installation is"
|
|
" ready for use by running:\n\n"
|
|
" $ %s 'hello world'\n", error_message,
|
|
args_string(cmd, cmd, NULL));
|
|
}
|
|
|
|
/* Do some sanity checks on palladiumd based on the output of `getnetworkinfo`. */
|
|
static void parse_getnetworkinfo_result(struct plugin *p, const char *buf)
|
|
{
|
|
const jsmntok_t *result;
|
|
bool tx_relay;
|
|
u32 min_version = 220000;
|
|
const char *err;
|
|
|
|
result = json_parse_simple(NULL, buf, strlen(buf));
|
|
if (!result)
|
|
plugin_err(p, "Invalid response to '%s': '%s'. Can not "
|
|
"continue without proceeding to sanity checks.",
|
|
args_string(tmpctx, gather_args(bitcoind, NULL, "getnetworkinfo", NULL), NULL),
|
|
buf);
|
|
|
|
/* Check that we have a fully-featured `estimatesmartfee`. */
|
|
err = json_scan(tmpctx, buf, result, "{version:%,localrelay:%}",
|
|
JSON_SCAN(json_to_u32, &bitcoind->version),
|
|
JSON_SCAN(json_to_bool, &tx_relay));
|
|
if (err)
|
|
plugin_err(p, "%s. Got '%.*s'. Can not"
|
|
" continue without proceeding to sanity checks.",
|
|
err,
|
|
json_tok_full_len(result), json_tok_full(buf, result));
|
|
|
|
if (bitcoind->version < min_version)
|
|
plugin_err(p, "Unsupported palladiumd version %"PRIu32", at least"
|
|
" %"PRIu32" required.", bitcoind->version, min_version);
|
|
|
|
/* We don't support 'blocksonly', as we rely on transaction relay for fee
|
|
* estimates. */
|
|
if (!tx_relay)
|
|
plugin_err(p, "The 'blocksonly' mode of palladiumd, or any option "
|
|
"deactivating transaction relay is not supported.");
|
|
|
|
tal_free(result);
|
|
}
|
|
|
|
/* Exit code 7 means RPC_IN_WARMUP: palladiumd is starting up but not ready yet */
|
|
#define RPC_IN_WARMUP_EXIT_CODE 7
|
|
|
|
static void wait_and_check_bitcoind(struct plugin *p)
|
|
{
|
|
struct bcli_result *res;
|
|
const char **cmd;
|
|
int retries = 0;
|
|
|
|
/* Special case: -rpcwait flags go on command line, not stdin */
|
|
cmd = gather_args(bitcoind, NULL, "-rpcwait",
|
|
"getnetworkinfo", NULL);
|
|
|
|
retry:
|
|
res = execute_bitcoin_cli(bitcoind, p, cmd, NULL);
|
|
|
|
/* palladiumd returns 7 (RPC_IN_WARMUP) while still starting up.
|
|
* Keep retrying for up to 60 seconds. */
|
|
if (res->exitstatus == RPC_IN_WARMUP_EXIT_CODE) {
|
|
if (retries++ < 60) {
|
|
sleep(1);
|
|
tal_free(res);
|
|
goto retry;
|
|
}
|
|
bitcoind_failure(p,
|
|
"palladiumd still warming up after 60s. "
|
|
"Is palladiumd fully started?");
|
|
}
|
|
|
|
if (res->exitstatus == 1)
|
|
bitcoind_failure(p,
|
|
"RPC connection timed out. Could "
|
|
"not connect to palladiumd using "
|
|
"palladium-cli. Is palladiumd running?");
|
|
if (res->exitstatus != 0)
|
|
bitcoind_failure(p,
|
|
tal_fmt(bitcoind, "%s exited with code %i: %s",
|
|
res->args, res->exitstatus, res->output));
|
|
|
|
parse_getnetworkinfo_result(p, res->output);
|
|
|
|
tal_free(cmd);
|
|
tal_free(res);
|
|
}
|
|
|
|
static void memleak_mark_bitcoind(struct plugin *p, struct htable *memtable)
|
|
{
|
|
memleak_scan_obj(memtable, bitcoind);
|
|
}
|
|
|
|
static const char *init(struct command *init_cmd, const char *buffer UNUSED,
|
|
const jsmntok_t *config UNUSED)
|
|
{
|
|
wait_and_check_bitcoind(init_cmd->plugin);
|
|
|
|
/* Usually we fake up fees in regtest */
|
|
if (streq(chainparams->network_name, "regtest"))
|
|
bitcoind->fake_fees = !bitcoind->dev_no_fake_fees;
|
|
else
|
|
bitcoind->fake_fees = false;
|
|
|
|
plugin_set_memleak_handler(init_cmd->plugin, memleak_mark_bitcoind);
|
|
plugin_log(init_cmd->plugin, LOG_INFORM,
|
|
"palladium-cli initialized and connected to palladiumd.");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static const struct plugin_command commands[] = {
|
|
{
|
|
"getrawblockbyheight",
|
|
getrawblockbyheight
|
|
},
|
|
{
|
|
"getchaininfo",
|
|
getchaininfo
|
|
},
|
|
{
|
|
"estimatefees",
|
|
estimatefees
|
|
},
|
|
{
|
|
"sendrawtransaction",
|
|
sendrawtransaction
|
|
},
|
|
{
|
|
"getutxout",
|
|
getutxout
|
|
},
|
|
};
|
|
|
|
static struct bitcoind *new_bitcoind(const tal_t *ctx)
|
|
{
|
|
bitcoind = tal(ctx, struct bitcoind);
|
|
|
|
bitcoind->cli = NULL;
|
|
bitcoind->datadir = NULL;
|
|
bitcoind->retry_timeout = 60;
|
|
bitcoind->rpcuser = NULL;
|
|
bitcoind->rpcpass = NULL;
|
|
bitcoind->rpcconnect = NULL;
|
|
bitcoind->rpcport = NULL;
|
|
/* Do not exceed retry_timeout value to avoid a bitcoind hang,
|
|
although normal rpcclienttimeout default value is 900. */
|
|
bitcoind->rpcclienttimeout = 60;
|
|
bitcoind->dev_no_fake_fees = false;
|
|
bitcoind->dev_ignore_ibd = false;
|
|
|
|
return bitcoind;
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
setup_locale();
|
|
|
|
/* Initialize our global context object here to handle startup options. */
|
|
bitcoind = new_bitcoind(NULL);
|
|
|
|
plugin_main(argv, init, NULL, PLUGIN_STATIC, false /* Do not init RPC on startup*/,
|
|
NULL, commands, ARRAY_SIZE(commands),
|
|
NULL, 0, NULL, 0, NULL, 0,
|
|
plugin_option("palladium-datadir",
|
|
"string",
|
|
"-datadir arg for palladium-cli",
|
|
charp_option, NULL, &bitcoind->datadir),
|
|
plugin_option("palladium-cli",
|
|
"string",
|
|
"palladium-cli pathname",
|
|
charp_option, NULL, &bitcoind->cli),
|
|
plugin_option("palladium-rpcuser",
|
|
"string",
|
|
"palladiumd RPC username",
|
|
charp_option, NULL, &bitcoind->rpcuser),
|
|
plugin_option("palladium-rpcpassword",
|
|
"string",
|
|
"palladiumd RPC password",
|
|
charp_option, NULL, &bitcoind->rpcpass),
|
|
plugin_option("palladium-rpcconnect",
|
|
"string",
|
|
"palladiumd RPC host to connect to",
|
|
charp_option, NULL, &bitcoind->rpcconnect),
|
|
plugin_option("palladium-rpcport",
|
|
"int",
|
|
"palladiumd RPC host's port",
|
|
charp_option, NULL, &bitcoind->rpcport),
|
|
plugin_option("palladium-rpcclienttimeout",
|
|
"int",
|
|
"palladiumd RPC timeout in seconds during HTTP requests",
|
|
u64_option, u64_jsonfmt, &bitcoind->rpcclienttimeout),
|
|
plugin_option("palladium-retry-timeout",
|
|
"int",
|
|
"how long to keep retrying to contact palladiumd"
|
|
" before fatally exiting",
|
|
u64_option, u64_jsonfmt, &bitcoind->retry_timeout),
|
|
plugin_option_dev("dev-no-fake-fees",
|
|
"bool",
|
|
"Suppress fee faking for regtest",
|
|
bool_option, NULL, &bitcoind->dev_no_fake_fees),
|
|
plugin_option_dev("dev-ignore-ibd",
|
|
"bool",
|
|
"Never tell lightningd we're doing initial block download",
|
|
bool_option, NULL, &bitcoind->dev_ignore_ibd),
|
|
NULL);
|
|
}
|