Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bitcoin backend: batch fee estimation requests #3570

Merged
2 changes: 1 addition & 1 deletion contrib/pyln-testing/pyln/testing/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -887,7 +887,7 @@ def force_feerates(self, rate):
self.set_feerates([rate] * 3, False)
self.restart()
self.daemon.wait_for_log('peer_out WIRE_UPDATE_FEE')
assert(self.rpc.feerates('perkw')['perkw']['normal'] == rate)
assert(self.rpc.feerates('perkw')['perkw']['opening'] == rate)

def wait_for_onchaind_broadcast(self, name, resolve=None):
"""Wait for onchaind to drop tx name to resolve (if any)"""
Expand Down
152 changes: 76 additions & 76 deletions lightningd/bitcoind.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
/* The names of the requests we can make to our Bitcoin backend. */
static const char *methods[] = {"getchaininfo", "getrawblockbyheight",
"sendrawtransaction", "getutxout",
"getfeerate"};
"estimatefees"};

static void plugin_config_cb(const char *buffer,
const jsmntok_t *toks,
Expand Down Expand Up @@ -113,9 +113,17 @@ void bitcoind_check_commands(struct bitcoind *bitcoind)
/* 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 *reason)
const char *fmt, ...)
{
struct plugin *p = strmap_get(&bitcoind->pluginsmap, method);
va_list ap;
char *reason;
struct plugin *p;

va_start(ap, fmt);
reason = tal_vfmt(NULL, fmt, ap);
va_end(ap);

p = strmap_get(&bitcoind->pluginsmap, method);
fatal("%s error: bad response to %s (%s), response was %.*s",
p->cmd, method, reason,
toks->end - toks->start, buf + toks->start);
Expand All @@ -133,118 +141,110 @@ static void bitcoin_plugin_send(struct bitcoind *bitcoind,
plugin_request_send(plugin, req);
}

/* `getfeerate`
/* `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:
* {
* "feerate": <fee per KB>,
* "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>,
* }
*/

struct estimatefee_call {
struct bitcoind *bitcoind;
size_t i;
const u32 *blocks;
const char **estmode;

void (*cb)(struct bitcoind *bitcoind, const u32 satoshi_per_kw[],
void *);
void *arg;
u32 *satoshi_per_kw;
};

static void do_one_estimatefee(struct bitcoind *bitcoind,
struct estimatefee_call *call);

static void getfeerate_callback(const char *buf, const jsmntok_t *toks,
const jsmntok_t *idtok,
struct estimatefee_call *call)
static void estimatefees_callback(const char *buf, const jsmntok_t *toks,
const jsmntok_t *idtok,
struct estimatefee_call *call)
{
const jsmntok_t *resulttok, *feeratetok;
u64 feerate;
u32 *feerates = tal_arr(call, u32, NUM_FEERATES);

resulttok = json_get_member(buf, toks, "result");
if (!resulttok)
bitcoin_plugin_error(call->bitcoind, buf, toks,
"getfeerate",
"estimatefees",
"bad 'result' field");

feeratetok = json_get_member(buf, resulttok, "feerate");
if (!feeratetok)
bitcoin_plugin_error(call->bitcoind, buf, toks,
"getfeerate",
"bad 'feerate' field");
for (enum feerate f = 0; f < NUM_FEERATES; f++) {
feeratetok = json_get_member(buf, resulttok, feerate_name(f));
if (!feeratetok)
bitcoin_plugin_error(call->bitcoind, buf, toks,
"estimatefees",
"missing '%s' field", feerate_name(f));

/* FIXME: We could trawl recent blocks for median fee... */
if (!json_to_u64(buf, feeratetok, &feerate)) {
log_unusual(call->bitcoind->log, "Unable to estimate %s/%u fee",
call->estmode[call->i], call->blocks[call->i]);
/* FIXME: We could trawl recent blocks for median fee... */
if (!json_to_u32(buf, feeratetok, &feerates[f])) {
log_unusual(call->bitcoind->log,
"Unable to estimate %s fees",
feerate_name(f));

#if DEVELOPER
/* This is needed to test for failed feerate estimates
* in DEVELOPER mode */
call->satoshi_per_kw[call->i] = 0;
/* This is needed to test for failed feerate estimates
* in DEVELOPER mode */
feerates[f] = 0;
#else
/* If we are in testnet mode we want to allow payments
* with the minimal fee even if the estimate didn't
* work out. This is less disruptive than erring out
* all the time. */
if (chainparams->testnet)
call->satoshi_per_kw[call->i] = FEERATE_FLOOR;
else
call->satoshi_per_kw[call->i] = 0;
/* If we are in testnet mode we want to allow payments
* with the minimal fee even if the estimate didn't
* work out. This is less disruptive than erring out
* all the time. */
if (chainparams->testnet)
feerates[f] = FEERATE_FLOOR;
else
feerates[f] = 0;
#endif
} else
/* Rate in satoshi per kw. */
call->satoshi_per_kw[call->i]
= feerate_from_style(feerate, FEERATE_PER_KBYTE);

call->i++;
if (call->i == tal_count(call->satoshi_per_kw)) {
call->cb(call->bitcoind, call->satoshi_per_kw, call->arg);
tal_free(call);
} else {
/* Next */
do_one_estimatefee(call->bitcoind, call);
} else
/* Rate in satoshi per kw. */
feerates[f] = feerate_from_style(feerates[f],
FEERATE_PER_KBYTE);
}
}

static void do_one_estimatefee(struct bitcoind *bitcoind,
struct estimatefee_call *call)
{
struct jsonrpc_request *req;

req = jsonrpc_request_start(bitcoind, "getfeerate",
bitcoind->log, getfeerate_callback,
call);
json_add_num(req->stream, "blocks", call->blocks[call->i]);
json_add_string(req->stream, "mode", call->estmode[call->i]);
jsonrpc_request_end(req);
bitcoin_plugin_send(bitcoind, req);
call->cb(call->bitcoind, feerates, call->arg);
tal_free(call);
}

void bitcoind_estimate_fees_(struct bitcoind *bitcoind,
const u32 blocks[], const char *estmode[],
size_t num_estimates,
void (*cb)(struct bitcoind *bitcoind,
const u32 satoshi_per_kw[], void *),
void *arg)
{
struct estimatefee_call *efee = tal(bitcoind, struct estimatefee_call);

efee->bitcoind = bitcoind;
efee->i = 0;
efee->blocks = tal_dup_arr(efee, u32, blocks, num_estimates, 0);
efee->estmode = tal_dup_arr(efee, const char *, estmode, num_estimates,
0);
efee->cb = cb;
efee->arg = arg;
efee->satoshi_per_kw = tal_arr(efee, u32, num_estimates);

do_one_estimatefee(bitcoind, efee);
struct jsonrpc_request *req;
struct estimatefee_call *call = tal(bitcoind, struct estimatefee_call);

call->bitcoind = bitcoind;
call->cb = cb;
call->arg = arg;

/* No parameter needed, we always want an urgent, normal and slow
* feerate. This gives computation flexibility to the plugin. */
req = jsonrpc_request_start(bitcoind, "estimatefees", bitcoind->log,
estimatefees_callback, call);
jsonrpc_request_end(req);
plugin_request_send(strmap_get(&bitcoind->pluginsmap,
"estimatefees"), req);
}

/* `sendrawtransaction`
Expand Down
5 changes: 2 additions & 3 deletions lightningd/bitcoind.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,13 @@ struct bitcoind *new_bitcoind(const tal_t *ctx,
struct log *log);

void bitcoind_estimate_fees_(struct bitcoind *bitcoind,
const u32 blocks[], const char *estmode[],
size_t num_estimates,
void (*cb)(struct bitcoind *bitcoind,
const u32 satoshi_per_kw[], void *),
void *arg);

#define bitcoind_estimate_fees(bitcoind_, blocks, estmode, num, cb, arg) \
bitcoind_estimate_fees_((bitcoind_), (blocks), (estmode), (num), \
#define bitcoind_estimate_fees(bitcoind_, num, cb, arg) \
bitcoind_estimate_fees_((bitcoind_), (num), \
typesafe_cb_preargs(void, void *, \
(cb), (arg), \
struct bitcoind *, \
Expand Down
Loading