mirror of
https://github.com/ElementsProject/lightning.git
synced 2024-11-19 18:11:28 +01:00
875 lines
26 KiB
C
875 lines
26 KiB
C
/* Code for talking to bitcoind. We use a plugin as the Bitcoin backend.
|
|
* The default one shipped with C-lightning is a plugin which talks to bitcoind
|
|
* by using bitcoin-cli, but the interface we use to gather Bitcoin data is
|
|
* standardized and you can use another plugin as the Bitcoin backend, or
|
|
* even make your own! */
|
|
#include "config.h"
|
|
#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/io/io.h>
|
|
#include <ccan/tal/str/str.h>
|
|
#include <common/configdir.h>
|
|
#include <common/json_parse.h>
|
|
#include <common/memleak.h>
|
|
#include <common/trace.h>
|
|
#include <db/exec.h>
|
|
#include <lightningd/bitcoind.h>
|
|
#include <lightningd/chaintopology.h>
|
|
#include <lightningd/io_loop_with_timers.h>
|
|
#include <lightningd/lightningd.h>
|
|
#include <lightningd/log.h>
|
|
#include <lightningd/plugin.h>
|
|
|
|
/* The names of the requests we can make to our Bitcoin backend. */
|
|
static const char *methods[] = {"getchaininfo", "getrawblockbyheight",
|
|
"sendrawtransaction", "getutxout",
|
|
"estimatefees"};
|
|
|
|
static void bitcoin_destructor(struct plugin *p)
|
|
{
|
|
if (p->plugins->ld->state == LD_STATE_SHUTDOWN)
|
|
return;
|
|
fatal("The Bitcoin backend died.");
|
|
}
|
|
|
|
static void plugin_config_cb(const char *buffer,
|
|
const jsmntok_t *toks,
|
|
const jsmntok_t *idtok,
|
|
struct plugin *plugin)
|
|
{
|
|
plugin->plugin_state = INIT_COMPLETE;
|
|
log_debug(plugin->plugins->ld->log, "io_break: %s", __func__);
|
|
io_break(plugin);
|
|
}
|
|
|
|
static void config_plugin(struct plugin *plugin)
|
|
{
|
|
struct jsonrpc_request *req;
|
|
void *ret;
|
|
|
|
req = jsonrpc_request_start(plugin, "init", NULL,
|
|
plugin->non_numeric_ids, plugin->log,
|
|
NULL, plugin_config_cb, plugin);
|
|
plugin_populate_init_request(plugin, req);
|
|
jsonrpc_request_end(req);
|
|
plugin_request_send(plugin, req);
|
|
|
|
tal_add_destructor(plugin, bitcoin_destructor);
|
|
|
|
ret = io_loop_with_timers(plugin->plugins->ld);
|
|
log_debug(plugin->plugins->ld->log, "io_loop_with_timers: %s", __func__);
|
|
assert(ret == plugin);
|
|
}
|
|
|
|
static void wait_plugin(struct bitcoind *bitcoind, const char *method,
|
|
struct plugin *p)
|
|
{
|
|
/* We need our Bitcoin backend to be initialized, but the plugins have
|
|
* not yet been started at this point.
|
|
* So send `init` to each plugin which registered for a Bitcoin method
|
|
* and wait for its response, which we take as an ACK that it is
|
|
* operational (i.e. bcli will wait for `bitcoind` to be warmed up
|
|
* before responding to `init`).
|
|
* Note that lightningd/plugin will not send `init` to an already
|
|
* configured plugin. */
|
|
if (p->plugin_state == NEEDS_INIT)
|
|
config_plugin(p);
|
|
|
|
strmap_add(&bitcoind->pluginsmap, method, p);
|
|
}
|
|
|
|
void bitcoind_check_commands(struct bitcoind *bitcoind)
|
|
{
|
|
size_t i;
|
|
struct plugin *p;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(methods); i++) {
|
|
p = find_plugin_for_command(bitcoind->ld, methods[i]);
|
|
if (p == NULL) {
|
|
/* For testing .. */
|
|
log_debug(bitcoind->ld->log, "Missing a Bitcoin plugin"
|
|
" command");
|
|
fatal("Could not access the plugin for %s, is a "
|
|
"Bitcoin plugin (by default plugins/bcli) "
|
|
"registered ?", methods[i]);
|
|
}
|
|
wait_plugin(bitcoind, methods[i], p);
|
|
}
|
|
}
|
|
|
|
/* Our Bitcoin backend plugin gave us a bad response. We can't recover. */
|
|
static void bitcoin_plugin_error(struct bitcoind *bitcoind, const char *buf,
|
|
const jsmntok_t *toks, const char *method,
|
|
const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
char *reason;
|
|
struct plugin *p;
|
|
|
|
va_start(ap, fmt);
|
|
reason = tal_vfmt(NULL, fmt, ap);
|
|
va_end(ap);
|
|
|
|
p = strmap_getn(&bitcoind->pluginsmap, method, strcspn(method, "."));
|
|
fatal("%s error: bad response to %s (%s), response was %.*s",
|
|
p ? p->cmd : "UNKNOWN CALL", method, reason,
|
|
toks->end - toks->start, buf + toks->start);
|
|
}
|
|
|
|
/* Send a request to the Bitcoin plugin which registered that method,
|
|
* if it's still alive. */
|
|
static void bitcoin_plugin_send(struct bitcoind *bitcoind,
|
|
struct jsonrpc_request *req)
|
|
{
|
|
struct plugin *plugin = strmap_get(&bitcoind->pluginsmap, req->method);
|
|
if (!plugin)
|
|
fatal("Bitcoin backend plugin for %s died.", req->method);
|
|
|
|
plugin_request_send(plugin, req);
|
|
}
|
|
|
|
/* `estimatefees`
|
|
*
|
|
* Gather feerate from our Bitcoin backend. Will set the feerate to `null`
|
|
* if estimation failed.
|
|
*
|
|
* - `opening` is used for funding and also misc transactions
|
|
* - `mutual_close` is used for the mutual close transaction
|
|
* - `unilateral_close` is used for unilateral close (commitment transactions)
|
|
* - `delayed_to_us` is used for resolving our output from our unilateral close
|
|
* - `htlc_resolution` is used for resolving onchain HTLCs
|
|
* - `penalty` is used for resolving revoked transactions
|
|
* - `min` is the minimum acceptable feerate
|
|
* - `max` is the maximum acceptable feerate
|
|
*
|
|
* Plugin response (deprecated):
|
|
* {
|
|
* "opening": <sat per kVB>,
|
|
* "mutual_close": <sat per kVB>,
|
|
* "unilateral_close": <sat per kVB>,
|
|
* "delayed_to_us": <sat per kVB>,
|
|
* "htlc_resolution": <sat per kVB>,
|
|
* "penalty": <sat per kVB>,
|
|
* "min_acceptable": <sat per kVB>,
|
|
* "max_acceptable": <sat per kVB>,
|
|
* }
|
|
*
|
|
* Plugin response (modern):
|
|
* {
|
|
* "feerate_floor": <sat per kVB>,
|
|
* "feerates": {
|
|
* { "blocks": 2, "feerate": <sat per kVB> },
|
|
* { "blocks": 6, "feerate": <sat per kVB> },
|
|
* { "blocks": 12, "feerate": <sat per kVB> }
|
|
* { "blocks": 100, "feerate": <sat per kVB> }
|
|
* }
|
|
* }
|
|
*
|
|
* If rates are missing, we linearly interpolate (we don't extrapolate tho!).
|
|
*/
|
|
struct estimatefee_call {
|
|
struct bitcoind *bitcoind;
|
|
void (*cb)(struct lightningd *ld, u32 feerate_floor,
|
|
const struct feerate_est *rates);
|
|
};
|
|
|
|
/* Note: returns estimates in perkb, caller converts! */
|
|
static struct feerate_est *parse_feerate_ranges(const tal_t *ctx,
|
|
struct bitcoind *bitcoind,
|
|
const char *buf,
|
|
const jsmntok_t *floortok,
|
|
const jsmntok_t *feerates,
|
|
u32 *floor)
|
|
{
|
|
size_t i;
|
|
const jsmntok_t *t;
|
|
struct feerate_est *rates = tal_arr(ctx, struct feerate_est, 0);
|
|
|
|
if (!json_to_u32(buf, floortok, floor))
|
|
bitcoin_plugin_error(bitcoind, buf, floortok,
|
|
"estimatefees.feerate_floor", "Not a u32?");
|
|
|
|
json_for_each_arr(i, t, feerates) {
|
|
struct feerate_est rate;
|
|
const char *err;
|
|
|
|
err = json_scan(tmpctx, buf, t, "{blocks:%,feerate:%}",
|
|
JSON_SCAN(json_to_u32, &rate.blockcount),
|
|
JSON_SCAN(json_to_u32, &rate.rate));
|
|
if (err)
|
|
bitcoin_plugin_error(bitcoind, buf, t,
|
|
"estimatefees.feerates", err);
|
|
|
|
/* Block count must be in order. If rates go up somehow, we
|
|
* reduce to prev. */
|
|
if (tal_count(rates) != 0) {
|
|
const struct feerate_est *prev = &rates[tal_count(rates)-1];
|
|
if (rate.blockcount <= prev->blockcount)
|
|
bitcoin_plugin_error(bitcoind, buf, feerates,
|
|
"estimatefees.feerates",
|
|
"Blocks must be ascending"
|
|
" order: %u <= %u!",
|
|
rate.blockcount,
|
|
prev->blockcount);
|
|
if (rate.rate > prev->rate) {
|
|
log_unusual(bitcoind->log,
|
|
"Feerate for %u blocks (%u) is > rate"
|
|
" for %u blocks (%u)!",
|
|
rate.blockcount, rate.rate,
|
|
prev->blockcount, prev->rate);
|
|
rate.rate = prev->rate;
|
|
}
|
|
}
|
|
|
|
tal_arr_expand(&rates, rate);
|
|
}
|
|
|
|
if (tal_count(rates) == 0) {
|
|
if (chainparams->testnet)
|
|
log_debug(bitcoind->log, "Unable to estimate any fees");
|
|
else
|
|
log_unusual(bitcoind->log, "Unable to estimate any fees");
|
|
}
|
|
|
|
return rates;
|
|
}
|
|
|
|
static struct feerate_est *parse_deprecated_feerates(const tal_t *ctx,
|
|
struct bitcoind *bitcoind,
|
|
const char *buf,
|
|
const jsmntok_t *toks)
|
|
{
|
|
struct feerate_est *rates = tal_arr(ctx, struct feerate_est, 0);
|
|
struct oldstyle {
|
|
const char *name;
|
|
size_t blockcount;
|
|
size_t multiplier;
|
|
} oldstyles[] = { { "max_acceptable", 2, 10 },
|
|
{ "unilateral_close", 6, 1 },
|
|
{ "opening", 12, 1 },
|
|
{ "mutual_close", 100, 1 } };
|
|
|
|
for (size_t i = 0; i < ARRAY_SIZE(oldstyles); i++) {
|
|
const jsmntok_t *feeratetok;
|
|
struct feerate_est rate;
|
|
|
|
feeratetok = json_get_member(buf, toks, oldstyles[i].name);
|
|
if (!feeratetok) {
|
|
bitcoin_plugin_error(bitcoind, buf, toks,
|
|
"estimatefees",
|
|
"missing '%s' field",
|
|
oldstyles[i].name);
|
|
}
|
|
if (!json_to_u32(buf, feeratetok, &rate.rate)) {
|
|
if (chainparams->testnet)
|
|
log_debug(bitcoind->log,
|
|
"Unable to estimate %s fees",
|
|
oldstyles[i].name);
|
|
else
|
|
log_unusual(bitcoind->log,
|
|
"Unable to estimate %s fees",
|
|
oldstyles[i].name);
|
|
continue;
|
|
}
|
|
|
|
if (rate.rate == 0)
|
|
continue;
|
|
|
|
/* Cancel out the 10x multiplier on max_acceptable */
|
|
rate.rate /= oldstyles[i].multiplier;
|
|
rate.blockcount = oldstyles[i].blockcount;
|
|
tal_arr_expand(&rates, rate);
|
|
}
|
|
return rates;
|
|
}
|
|
|
|
static void estimatefees_callback(const char *buf, const jsmntok_t *toks,
|
|
const jsmntok_t *idtok,
|
|
struct estimatefee_call *call)
|
|
{
|
|
const jsmntok_t *resulttok, *floortok;
|
|
struct feerate_est *feerates;
|
|
u32 floor;
|
|
|
|
resulttok = json_get_member(buf, toks, "result");
|
|
if (!resulttok)
|
|
bitcoin_plugin_error(call->bitcoind, buf, toks,
|
|
"estimatefees",
|
|
"bad 'result' field");
|
|
|
|
/* Modern style has floor. */
|
|
floortok = json_get_member(buf, resulttok, "feerate_floor");
|
|
if (floortok) {
|
|
feerates = parse_feerate_ranges(call, call->bitcoind,
|
|
buf, floortok,
|
|
json_get_member(buf, resulttok,
|
|
"feerates"),
|
|
&floor);
|
|
} else {
|
|
if (!call->bitcoind->ld->deprecated_apis)
|
|
bitcoin_plugin_error(call->bitcoind, buf, resulttok,
|
|
"estimatefees",
|
|
"missing fee_floor field");
|
|
|
|
feerates = parse_deprecated_feerates(call, call->bitcoind,
|
|
buf, resulttok);
|
|
floor = feerate_from_style(FEERATE_FLOOR, FEERATE_PER_KSIPA);
|
|
}
|
|
|
|
/* Convert to perkw */
|
|
floor = feerate_from_style(floor, FEERATE_PER_KBYTE);
|
|
if (floor < FEERATE_FLOOR)
|
|
floor = FEERATE_FLOOR;
|
|
|
|
/* FIXME: We could let this go below the dynamic floor, but we'd
|
|
* need to know if the floor is because of their node's policy
|
|
* (minrelaytxfee) or mempool conditions (mempoolminfee). */
|
|
for (size_t i = 0; i < tal_count(feerates); i++) {
|
|
feerates[i].rate = feerate_from_style(feerates[i].rate,
|
|
FEERATE_PER_KBYTE);
|
|
if (feerates[i].rate < floor)
|
|
feerates[i].rate = floor;
|
|
}
|
|
|
|
call->cb(call->bitcoind->ld, floor, feerates);
|
|
tal_free(call);
|
|
}
|
|
|
|
void bitcoind_estimate_fees(struct bitcoind *bitcoind,
|
|
void (*cb)(struct lightningd *ld,
|
|
u32 feerate_floor,
|
|
const struct feerate_est *feerates))
|
|
{
|
|
struct jsonrpc_request *req;
|
|
struct estimatefee_call *call = tal(bitcoind, struct estimatefee_call);
|
|
|
|
call->bitcoind = bitcoind;
|
|
call->cb = cb;
|
|
|
|
req = jsonrpc_request_start(bitcoind, "estimatefees", NULL, true,
|
|
bitcoind->log,
|
|
NULL, estimatefees_callback, call);
|
|
jsonrpc_request_end(req);
|
|
plugin_request_send(strmap_get(&bitcoind->pluginsmap,
|
|
"estimatefees"), req);
|
|
}
|
|
|
|
/* `sendrawtransaction`
|
|
*
|
|
* Send a transaction to the Bitcoin backend plugin. If the broadcast was
|
|
* not successful on its end, the plugin will populate the `errmsg` with
|
|
* the reason.
|
|
*
|
|
* Plugin response:
|
|
* {
|
|
* "success": <true|false>,
|
|
* "errmsg": "<not empty if !success>"
|
|
* }
|
|
*/
|
|
|
|
struct sendrawtx_call {
|
|
struct bitcoind *bitcoind;
|
|
void (*cb)(struct bitcoind *bitcoind,
|
|
bool success,
|
|
const char *err_msg,
|
|
void *);
|
|
void *cb_arg;
|
|
};
|
|
|
|
static void sendrawtx_callback(const char *buf, const jsmntok_t *toks,
|
|
const jsmntok_t *idtok,
|
|
struct sendrawtx_call *call)
|
|
{
|
|
const char *err;
|
|
const char *errmsg = NULL;
|
|
bool success = false;
|
|
|
|
err = json_scan(tmpctx, buf, toks, "{result:{success:%}}",
|
|
JSON_SCAN(json_to_bool, &success));
|
|
if (err) {
|
|
bitcoin_plugin_error(call->bitcoind, buf, toks,
|
|
"sendrawtransaction",
|
|
"bad 'result' field: %s", err);
|
|
} else if (!success) {
|
|
err = json_scan(tmpctx, buf, toks, "{result:{errmsg:%}}",
|
|
JSON_SCAN_TAL(tmpctx, json_strdup, &errmsg));
|
|
if (err)
|
|
bitcoin_plugin_error(call->bitcoind, buf, toks,
|
|
"sendrawtransaction",
|
|
"bad 'errmsg' field: %s",
|
|
err);
|
|
}
|
|
|
|
db_begin_transaction(call->bitcoind->ld->wallet->db);
|
|
call->cb(call->bitcoind, success, errmsg, call->cb_arg);
|
|
db_commit_transaction(call->bitcoind->ld->wallet->db);
|
|
|
|
tal_free(call);
|
|
}
|
|
|
|
void bitcoind_sendrawtx_(struct bitcoind *bitcoind,
|
|
const char *id_prefix,
|
|
const char *hextx,
|
|
bool allowhighfees,
|
|
void (*cb)(struct bitcoind *bitcoind,
|
|
bool success, const char *msg, void *),
|
|
void *cb_arg)
|
|
{
|
|
struct jsonrpc_request *req;
|
|
struct sendrawtx_call *call = tal(bitcoind, struct sendrawtx_call);
|
|
|
|
call->bitcoind = bitcoind;
|
|
call->cb = cb;
|
|
call->cb_arg = cb_arg;
|
|
log_debug(bitcoind->log, "sendrawtransaction: %s", hextx);
|
|
|
|
req = jsonrpc_request_start(bitcoind, "sendrawtransaction",
|
|
id_prefix, true,
|
|
bitcoind->log,
|
|
NULL, sendrawtx_callback,
|
|
call);
|
|
json_add_string(req->stream, "tx", hextx);
|
|
json_add_bool(req->stream, "allowhighfees", allowhighfees);
|
|
jsonrpc_request_end(req);
|
|
bitcoin_plugin_send(bitcoind, req);
|
|
}
|
|
|
|
/* `getrawblockbyheight`
|
|
*
|
|
* If no block were found at that height, will set each field to `null`.
|
|
* Plugin response:
|
|
* {
|
|
* "blockhash": "<blkid>",
|
|
* "block": "rawblock"
|
|
* }
|
|
*/
|
|
|
|
struct getrawblockbyheight_call {
|
|
struct bitcoind *bitcoind;
|
|
void (*cb)(struct bitcoind *bitcoind,
|
|
struct bitcoin_blkid *blkid,
|
|
struct bitcoin_block *block,
|
|
void *);
|
|
void *cb_arg;
|
|
};
|
|
|
|
static void
|
|
getrawblockbyheight_callback(const char *buf, const jsmntok_t *toks,
|
|
const jsmntok_t *idtok,
|
|
struct getrawblockbyheight_call *call)
|
|
{
|
|
const char *block_str, *err;
|
|
struct bitcoin_blkid blkid;
|
|
struct bitcoin_block *blk;
|
|
trace_span_resume(call);
|
|
trace_span_end(call);
|
|
|
|
/* If block hash is `null`, this means not found! Call the callback
|
|
* with NULL values. */
|
|
err = json_scan(tmpctx, buf, toks, "{result:{blockhash:null}}");
|
|
if (!err) {
|
|
db_begin_transaction(call->bitcoind->ld->wallet->db);
|
|
call->cb(call->bitcoind, NULL, NULL, call->cb_arg);
|
|
db_commit_transaction(call->bitcoind->ld->wallet->db);
|
|
goto clean;
|
|
}
|
|
|
|
err = json_scan(tmpctx, buf, toks, "{result:{blockhash:%,block:%}}",
|
|
JSON_SCAN(json_to_sha256, &blkid.shad.sha),
|
|
JSON_SCAN_TAL(tmpctx, json_strdup, &block_str));
|
|
if (err)
|
|
bitcoin_plugin_error(call->bitcoind, buf, toks,
|
|
"getrawblockbyheight",
|
|
"bad 'result' field: %s", err);
|
|
|
|
blk = bitcoin_block_from_hex(tmpctx, chainparams, block_str,
|
|
strlen(block_str));
|
|
if (!blk)
|
|
bitcoin_plugin_error(call->bitcoind, buf, toks,
|
|
"getrawblockbyheight",
|
|
"bad block");
|
|
|
|
db_begin_transaction(call->bitcoind->ld->wallet->db);
|
|
call->cb(call->bitcoind, &blkid, blk, call->cb_arg);
|
|
db_commit_transaction(call->bitcoind->ld->wallet->db);
|
|
|
|
clean:
|
|
tal_free(call);
|
|
}
|
|
|
|
void bitcoind_getrawblockbyheight_(struct bitcoind *bitcoind,
|
|
u32 height,
|
|
void (*cb)(struct bitcoind *bitcoind,
|
|
struct bitcoin_blkid *blkid,
|
|
struct bitcoin_block *blk,
|
|
void *arg),
|
|
void *cb_arg)
|
|
{
|
|
struct jsonrpc_request *req;
|
|
struct getrawblockbyheight_call *call = tal(NULL,
|
|
struct getrawblockbyheight_call);
|
|
|
|
call->bitcoind = bitcoind;
|
|
call->cb = cb;
|
|
call->cb_arg = cb_arg;
|
|
|
|
trace_span_start("plugin/bitcoind", call);
|
|
trace_span_tag(call, "method", "getrawblockbyheight");
|
|
trace_span_suspend(call);
|
|
req = jsonrpc_request_start(bitcoind, "getrawblockbyheight", NULL, true,
|
|
bitcoind->log,
|
|
NULL, getrawblockbyheight_callback,
|
|
call);
|
|
json_add_num(req->stream, "height", height);
|
|
jsonrpc_request_end(req);
|
|
bitcoin_plugin_send(bitcoind, req);
|
|
}
|
|
|
|
/* `getchaininfo`
|
|
*
|
|
* Called at startup to check the network we are operating on, and to check
|
|
* if the Bitcoin backend is synced to the network tip. This also allows to
|
|
* get the current block count.
|
|
* {
|
|
* "chain": "<bip70_chainid>",
|
|
* "headercount": <number of fetched headers>,
|
|
* "blockcount": <number of fetched block>,
|
|
* "ibd": <synced?>
|
|
* }
|
|
*/
|
|
|
|
struct getchaininfo_call {
|
|
struct bitcoind *bitcoind;
|
|
/* Should we log verbosely? */
|
|
bool first_call;
|
|
void (*cb)(struct bitcoind *bitcoind,
|
|
const char *chain,
|
|
u32 headercount,
|
|
u32 blockcount,
|
|
const bool ibd,
|
|
const bool first_call,
|
|
void *);
|
|
void *cb_arg;
|
|
};
|
|
|
|
static void getchaininfo_callback(const char *buf, const jsmntok_t *toks,
|
|
const jsmntok_t *idtok,
|
|
struct getchaininfo_call *call)
|
|
{
|
|
const char *err, *chain;
|
|
u32 headers, blocks;
|
|
bool ibd;
|
|
|
|
err = json_scan(tmpctx, buf, toks,
|
|
"{result:{chain:%,headercount:%,blockcount:%,ibd:%}}",
|
|
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)
|
|
bitcoin_plugin_error(call->bitcoind, buf, toks, "getchaininfo",
|
|
"bad 'result' field: %s", err);
|
|
|
|
db_begin_transaction(call->bitcoind->ld->wallet->db);
|
|
call->cb(call->bitcoind, chain, headers, blocks, ibd,
|
|
call->first_call, call->cb_arg);
|
|
db_commit_transaction(call->bitcoind->ld->wallet->db);
|
|
|
|
tal_free(call);
|
|
}
|
|
|
|
void bitcoind_getchaininfo_(struct bitcoind *bitcoind,
|
|
const bool first_call,
|
|
const u32 height,
|
|
void (*cb)(struct bitcoind *bitcoind,
|
|
const char *chain,
|
|
u32 headercount,
|
|
u32 blockcount,
|
|
const bool ibd,
|
|
const bool first_call,
|
|
void *),
|
|
void *cb_arg)
|
|
{
|
|
struct jsonrpc_request *req;
|
|
struct getchaininfo_call *call = tal(bitcoind, struct getchaininfo_call);
|
|
|
|
call->bitcoind = bitcoind;
|
|
call->cb = cb;
|
|
call->cb_arg = cb_arg;
|
|
call->first_call = first_call;
|
|
|
|
req = jsonrpc_request_start(bitcoind, "getchaininfo", NULL, true,
|
|
bitcoind->log,
|
|
NULL, getchaininfo_callback, call);
|
|
json_add_u32(req->stream, "last_height", height);
|
|
jsonrpc_request_end(req);
|
|
bitcoin_plugin_send(bitcoind, req);
|
|
}
|
|
|
|
/* `getutxout`
|
|
*
|
|
* Get information about an UTXO. If the TXO is spent, the plugin will set
|
|
* all fields to `null`.
|
|
* {
|
|
* "amount": <The output's amount in *sats*>,
|
|
* "script": "The output's scriptPubKey",
|
|
* }
|
|
*/
|
|
|
|
struct getutxout_call {
|
|
struct bitcoind *bitcoind;
|
|
unsigned int blocknum, txnum, outnum;
|
|
|
|
/* The real callback */
|
|
void (*cb)(struct bitcoind *bitcoind,
|
|
const struct bitcoin_tx_output *txout, void *arg);
|
|
/* The real callback arg */
|
|
void *cb_arg;
|
|
};
|
|
|
|
static void getutxout_callback(const char *buf, const jsmntok_t *toks,
|
|
const jsmntok_t *idtok,
|
|
struct getutxout_call *call)
|
|
{
|
|
const char *err;
|
|
struct bitcoin_tx_output txout;
|
|
|
|
err = json_scan(tmpctx, buf, toks, "{result:{script:null}}");
|
|
if (!err) {
|
|
db_begin_transaction(call->bitcoind->ld->wallet->db);
|
|
call->cb(call->bitcoind, NULL, call->cb_arg);
|
|
db_commit_transaction(call->bitcoind->ld->wallet->db);
|
|
goto clean;
|
|
}
|
|
|
|
err = json_scan(tmpctx, buf, toks, "{result:{script:%,amount:%}}",
|
|
JSON_SCAN_TAL(tmpctx, json_tok_bin_from_hex,
|
|
&txout.script),
|
|
JSON_SCAN(json_to_sat, &txout.amount));
|
|
if (err)
|
|
bitcoin_plugin_error(call->bitcoind, buf, toks, "getutxout",
|
|
"bad 'result' field: %s", err);
|
|
|
|
db_begin_transaction(call->bitcoind->ld->wallet->db);
|
|
call->cb(call->bitcoind, &txout, call->cb_arg);
|
|
db_commit_transaction(call->bitcoind->ld->wallet->db);
|
|
|
|
clean:
|
|
tal_free(call);
|
|
}
|
|
|
|
void bitcoind_getutxout_(struct bitcoind *bitcoind,
|
|
const struct bitcoin_outpoint *outpoint,
|
|
void (*cb)(struct bitcoind *,
|
|
const struct bitcoin_tx_output *,
|
|
void *),
|
|
void *cb_arg)
|
|
{
|
|
struct jsonrpc_request *req;
|
|
struct getutxout_call *call = tal(bitcoind, struct getutxout_call);
|
|
|
|
call->bitcoind = bitcoind;
|
|
call->cb = cb;
|
|
call->cb_arg = cb_arg;
|
|
|
|
req = jsonrpc_request_start(bitcoind, "getutxout", NULL, true,
|
|
bitcoind->log,
|
|
NULL, getutxout_callback, call);
|
|
json_add_txid(req->stream, "txid", &outpoint->txid);
|
|
json_add_num(req->stream, "vout", outpoint->n);
|
|
jsonrpc_request_end(req);
|
|
bitcoin_plugin_send(bitcoind, req);
|
|
}
|
|
|
|
/* Context for the getfilteredblock call. Wraps the actual arguments while we
|
|
* process the various steps. */
|
|
struct filteredblock_call {
|
|
struct list_node list;
|
|
void (*cb)(struct bitcoind *bitcoind, const struct filteredblock *fb,
|
|
void *arg);
|
|
void *arg;
|
|
|
|
struct filteredblock *result;
|
|
struct filteredblock_outpoint **outpoints;
|
|
size_t current_outpoint;
|
|
struct timeabs start_time;
|
|
u32 height;
|
|
};
|
|
|
|
/* Declaration for recursion in process_getfilteredblock_step1 */
|
|
static void
|
|
process_getfiltered_block_final(struct bitcoind *bitcoind,
|
|
const struct filteredblock_call *call);
|
|
|
|
static void
|
|
process_getfilteredblock_step2(struct bitcoind *bitcoind,
|
|
const struct bitcoin_tx_output *output,
|
|
void *arg)
|
|
{
|
|
struct filteredblock_call *call = (struct filteredblock_call *)arg;
|
|
struct filteredblock_outpoint *o = call->outpoints[call->current_outpoint];
|
|
|
|
/* If this output is unspent, add it to the filteredblock result. */
|
|
if (output)
|
|
tal_arr_expand(&call->result->outpoints, tal_steal(call->result, o));
|
|
|
|
call->current_outpoint++;
|
|
if (call->current_outpoint < tal_count(call->outpoints)) {
|
|
o = call->outpoints[call->current_outpoint];
|
|
bitcoind_getutxout(bitcoind, &o->outpoint,
|
|
process_getfilteredblock_step2, call);
|
|
} else {
|
|
/* If there were no more outpoints to check, we call the callback. */
|
|
process_getfiltered_block_final(bitcoind, call);
|
|
}
|
|
}
|
|
|
|
static void process_getfilteredblock_step1(struct bitcoind *bitcoind,
|
|
struct bitcoin_blkid *blkid,
|
|
struct bitcoin_block *block,
|
|
struct filteredblock_call *call)
|
|
{
|
|
struct filteredblock_outpoint *o;
|
|
struct bitcoin_tx *tx;
|
|
|
|
/* If we were unable to fetch the block hash (bitcoind doesn't know
|
|
* about a block at that height), we can short-circuit and just call
|
|
* the callback. */
|
|
if (!blkid)
|
|
return process_getfiltered_block_final(bitcoind, call);
|
|
|
|
/* So we have the first piece of the puzzle, the block hash */
|
|
call->result = tal(call, struct filteredblock);
|
|
call->result->height = call->height;
|
|
call->result->outpoints = tal_arr(call->result, struct filteredblock_outpoint *, 0);
|
|
call->result->id = *blkid;
|
|
|
|
/* If the plugin gave us a block id, they MUST send us a block. */
|
|
assert(block != NULL);
|
|
|
|
call->result->prev_hash = block->hdr.prev_hash;
|
|
|
|
/* Allocate an array containing all the potentially interesting
|
|
* outpoints. We will later copy the ones we're interested in into the
|
|
* call->result if they are unspent. */
|
|
|
|
call->outpoints = tal_arr(call, struct filteredblock_outpoint *, 0);
|
|
for (size_t i = 0; i < tal_count(block->tx); i++) {
|
|
tx = block->tx[i];
|
|
for (size_t j = 0; j < tx->wtx->num_outputs; j++) {
|
|
const u8 *script = bitcoin_tx_output_get_script(NULL, tx, j);
|
|
struct amount_asset amount = bitcoin_tx_output_get_amount(tx, j);
|
|
if (amount_asset_is_main(&amount) && is_p2wsh(script, NULL)) {
|
|
/* This is an interesting output, remember it. */
|
|
o = tal(call->outpoints, struct filteredblock_outpoint);
|
|
bitcoin_txid(tx, &o->outpoint.txid);
|
|
o->outpoint.n = j;
|
|
o->amount = amount_asset_to_sat(&amount);
|
|
o->txindex = i;
|
|
o->scriptPubKey = tal_steal(o, script);
|
|
tal_arr_expand(&call->outpoints, o);
|
|
} else {
|
|
tal_free(script);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (tal_count(call->outpoints) == 0) {
|
|
/* If there were no outpoints to check, we can short-circuit
|
|
* and just call the callback. */
|
|
process_getfiltered_block_final(bitcoind, call);
|
|
} else {
|
|
|
|
/* Otherwise we start iterating through call->outpoints and
|
|
* store the one's that are unspent in
|
|
* call->result->outpoints. */
|
|
o = call->outpoints[call->current_outpoint];
|
|
bitcoind_getutxout(bitcoind, &o->outpoint,
|
|
process_getfilteredblock_step2, call);
|
|
}
|
|
}
|
|
|
|
/* Takes a call, dispatches it to all queued requests that match the same
|
|
* height, and then kicks off the next call. */
|
|
static void
|
|
process_getfiltered_block_final(struct bitcoind *bitcoind,
|
|
const struct filteredblock_call *call)
|
|
{
|
|
struct filteredblock_call *c, *next;
|
|
u32 height = call->height;
|
|
|
|
if (call->result == NULL)
|
|
goto next;
|
|
|
|
/* Need to steal so we don't accidentally free it while iterating through the list below. */
|
|
struct filteredblock *fb = tal_steal(NULL, call->result);
|
|
list_for_each_safe(&bitcoind->pending_getfilteredblock, c, next, list) {
|
|
if (c->height == height) {
|
|
c->cb(bitcoind, fb, c->arg);
|
|
list_del(&c->list);
|
|
tal_free(c);
|
|
}
|
|
}
|
|
tal_free(fb);
|
|
|
|
next:
|
|
/* Nothing to free here, since `*call` was already deleted during the
|
|
* iteration above. It was also removed from the list, so no need to
|
|
* pop here. */
|
|
if (!list_empty(&bitcoind->pending_getfilteredblock)) {
|
|
c = list_top(&bitcoind->pending_getfilteredblock, struct filteredblock_call, list);
|
|
bitcoind_getrawblockbyheight(bitcoind, c->height,
|
|
process_getfilteredblock_step1, c);
|
|
}
|
|
}
|
|
|
|
void bitcoind_getfilteredblock_(struct bitcoind *bitcoind, u32 height,
|
|
void (*cb)(struct bitcoind *bitcoind,
|
|
const struct filteredblock *fb,
|
|
void *arg),
|
|
void *arg)
|
|
{
|
|
/* Stash the call context for when we need to call the callback after
|
|
* all the bitcoind calls we need to perform. */
|
|
struct filteredblock_call *call = tal(bitcoind, struct filteredblock_call);
|
|
/* If this is the first request, we should start processing it. */
|
|
bool start = list_empty(&bitcoind->pending_getfilteredblock);
|
|
call->cb = cb;
|
|
call->arg = arg;
|
|
call->height = height;
|
|
assert(call->cb != NULL);
|
|
call->start_time = time_now();
|
|
call->result = NULL;
|
|
call->current_outpoint = 0;
|
|
|
|
list_add_tail(&bitcoind->pending_getfilteredblock, &call->list);
|
|
if (start)
|
|
bitcoind_getrawblockbyheight(bitcoind, height,
|
|
process_getfilteredblock_step1, call);
|
|
}
|
|
|
|
static void destroy_bitcoind(struct bitcoind *bitcoind)
|
|
{
|
|
strmap_clear(&bitcoind->pluginsmap);
|
|
}
|
|
|
|
struct bitcoind *new_bitcoind(const tal_t *ctx,
|
|
struct lightningd *ld,
|
|
struct logger *log)
|
|
{
|
|
struct bitcoind *bitcoind = tal(ctx, struct bitcoind);
|
|
|
|
strmap_init(&bitcoind->pluginsmap);
|
|
bitcoind->ld = ld;
|
|
bitcoind->log = log;
|
|
list_head_init(&bitcoind->pending_getfilteredblock);
|
|
tal_add_destructor(bitcoind, destroy_bitcoind);
|
|
bitcoind->synced = false;
|
|
|
|
return bitcoind;
|
|
}
|