bcli: adapt interface to the new fees estimation interface

We keep the same behaviour as lightningd before.
This commit is contained in:
darosior 2020-03-05 12:00:57 +01:00 committed by Rusty Russell
parent d4fe4073a4
commit 5e72b22e80
2 changed files with 130 additions and 31 deletions

View file

@ -449,17 +449,36 @@ static struct command_result *process_getblockchaininfo(struct bitcoin_cli *bcli
return command_finished(bcli->cmd, response); return command_finished(bcli->cmd, response);
} }
static struct command_result *process_estimatefee(struct bitcoin_cli *bcli)
struct estimatefees_stash {
/* FIXME: We use u64 but lightningd will store them as u32. */
u64 urgent, normal, slow;
};
static struct command_result *
estimatefees_null_response(struct bitcoin_cli *bcli)
{
struct json_stream *response = jsonrpc_stream_success(bcli->cmd);
json_add_null(response, "opening");
json_add_null(response, "mutual_close");
json_add_null(response, "unilateral_close");
json_add_null(response, "delayed_to_us");
json_add_null(response, "htlc_resolution");
json_add_null(response, "penalty");
json_add_null(response, "min_acceptable");
json_add_null(response, "max_acceptable");
return command_finished(bcli->cmd, response);
}
static struct command_result *
estimatefees_parse_feerate(struct bitcoin_cli *bcli, u64 *feerate)
{ {
const jsmntok_t *tokens, *feeratetok = NULL; const jsmntok_t *tokens, *feeratetok = NULL;
struct json_stream *response;
bool valid; bool valid;
u64 feerate;
char *err; char *err;
if (*bcli->exitstatus != 0)
goto end;
tokens = json_parse_input(bcli->output, bcli->output, tokens = json_parse_input(bcli->output, bcli->output,
(int)bcli->output_bytes, &valid); (int)bcli->output_bytes, &valid);
if (!tokens) { if (!tokens) {
@ -478,23 +497,96 @@ static struct command_result *process_estimatefee(struct bitcoin_cli *bcli)
feeratetok = json_get_member(bcli->output, tokens, "feerate"); feeratetok = json_get_member(bcli->output, tokens, "feerate");
if (feeratetok && if (feeratetok &&
!json_to_bitcoin_amount(bcli->output, feeratetok, &feerate)) { !json_to_bitcoin_amount(bcli->output, feeratetok, feerate)) {
err = tal_fmt(bcli->cmd, "%s: bad 'feerate' field (%.*s)", err = tal_fmt(bcli->cmd, "%s: bad 'feerate' field (%.*s)",
bcli_args(bcli), (int)bcli->output_bytes, bcli_args(bcli), (int)bcli->output_bytes,
bcli->output); bcli->output);
return command_done_err(bcli->cmd, BCLI_ERROR, err, NULL); return command_done_err(bcli->cmd, BCLI_ERROR, err, NULL);
} else if (!feeratetok)
/* We return null if estimation failed, and bitcoin-cli will
* exit with 0 but no feerate field on failure. */
return estimatefees_null_response(bcli);
return NULL;
} }
end: /* We got all the feerates, give them to lightningd. */
static struct command_result *estimatefees_final_step(struct bitcoin_cli *bcli)
{
struct command_result *err;
struct json_stream *response;
struct estimatefees_stash *stash = bcli->stash;
/* bitcoind could theoretically fail to estimate for a higher target. */
if (*bcli->exitstatus != 0)
return estimatefees_null_response(bcli);
err = estimatefees_parse_feerate(bcli, &stash->slow);
if (err)
return err;
response = jsonrpc_stream_success(bcli->cmd); response = jsonrpc_stream_success(bcli->cmd);
if (feeratetok) json_add_u64(response, "opening", stash->normal);
json_add_u64(response, "feerate", feerate); json_add_u64(response, "mutual_close", stash->normal);
else json_add_u64(response, "unilateral_close", stash->urgent);
json_add_null(response, "feerate"); json_add_u64(response, "delayed_to_us", stash->normal);
json_add_u64(response, "htlc_resolution", stash->normal);
json_add_u64(response, "penalty", stash->normal);
/* We divide the slow feerate for the minimum acceptable, lightningd
* will use floor if it's hit, though. */
json_add_u64(response, "min_acceptable", stash->slow / 2);
json_add_u64(response, "max_acceptable", stash->urgent);
return command_finished(bcli->cmd, response); return command_finished(bcli->cmd, response);
} }
/* We got the response for the normal feerate, now treat the slow one. */
static struct command_result *estimatefees_third_step(struct bitcoin_cli *bcli)
{
struct command_result *err;
struct estimatefees_stash *stash = bcli->stash;
const char **params = tal_arr(bcli->cmd, const char *, 2);
/* bitcoind could theoretically fail to estimate for a higher target. */
if (*bcli->exitstatus != 0)
return estimatefees_null_response(bcli);
err = estimatefees_parse_feerate(bcli, &stash->normal);
if (err)
return err;
params[0] = "100";
params[1] = "ECONOMICAL";
start_bitcoin_cli(NULL, bcli->cmd, estimatefees_final_step, true,
BITCOIND_LOW_PRIO, "estimatesmartfee", params, stash);
return command_still_pending(bcli->cmd);
}
/* We got the response for the urgent feerate, now treat the normal one. */
static struct command_result *estimatefees_second_step(struct bitcoin_cli *bcli)
{
struct command_result *err;
struct estimatefees_stash *stash = bcli->stash;
const char **params = tal_arr(bcli->cmd, const char *, 2);
/* If we cannot estimatefees, no need to continue bothering bitcoind. */
if (*bcli->exitstatus != 0)
return estimatefees_null_response(bcli);
err = estimatefees_parse_feerate(bcli, &stash->urgent);
if (err)
return err;
params[0] = "4";
params[1] = "ECONOMICAL";
start_bitcoin_cli(NULL, bcli->cmd, estimatefees_third_step, true,
BITCOIND_LOW_PRIO, "estimatesmartfee", params, stash);
return command_still_pending(bcli->cmd);
}
static struct command_result *process_sendrawtransaction(struct bitcoin_cli *bcli) static struct command_result *process_sendrawtransaction(struct bitcoin_cli *bcli)
{ {
struct json_stream *response; struct json_stream *response;
@ -623,25 +715,28 @@ static struct command_result *getchaininfo(struct command *cmd,
return command_still_pending(cmd); return command_still_pending(cmd);
} }
/* Get current feerate. /* Get the current feerates. We use an urgent feerate for unilateral_close and max,
* Calls `estimatesmartfee` and returns the feerate as btc/k*VBYTE*. * a slow feerate for min, and a normal for all others.
*
* Calls `estimatesmartfee` with targets 2/CONSERVATIVE (urgent),
* 4/ECONOMICAL (normal), and 100/ECONOMICAL (slow) then returns the
* feerates as sat/kVB.
*/ */
static struct command_result *getfeerate(struct command *cmd, static struct command_result *estimatefees(struct command *cmd,
const char *buf UNUSED, const char *buf UNUSED,
const jsmntok_t *toks UNUSED) const jsmntok_t *toks UNUSED)
{ {
u32 *blocks; struct estimatefees_stash *stash = tal(cmd, struct estimatefees_stash);
const char **params = tal_arr(cmd, const char *, 2); const char **params = tal_arr(cmd, const char *, 2);
if (!param(cmd, buf, toks, if (!param(cmd, buf, toks, NULL))
p_req("blocks", param_number, &blocks),
p_req("mode", param_string, &params[1]),
NULL))
return command_param_failed(); return command_param_failed();
params[0] = tal_fmt(params, "%u", *blocks); /* First call to estimatesmartfee, for urgent estimation. */
start_bitcoin_cli(NULL, cmd, process_estimatefee, true, params[0] = "2";
BITCOIND_LOW_PRIO, "estimatesmartfee", params, NULL); params[1] = "CONSERVATIVE";
start_bitcoin_cli(NULL, cmd, estimatefees_second_step, true,
BITCOIND_LOW_PRIO, "estimatesmartfee", params, stash);
return command_still_pending(cmd); return command_still_pending(cmd);
} }
@ -773,11 +868,12 @@ static const struct plugin_command commands[] = {
getchaininfo getchaininfo
}, },
{ {
"getfeerate", "estimatefees",
"bitcoin", "bitcoin",
"Get the Bitcoin feerate in btc/kilo-vbyte.", "Get the urgent, normal and slow Bitcoin feerates as"
" sat/kVB.",
"", "",
getfeerate estimatefees
}, },
{ {
"sendrawtransaction", "sendrawtransaction",

View file

@ -1071,8 +1071,11 @@ def test_bcli(node_factory, bitcoind, chainparams):
l1.rpc.plugin_stop("bcli") l1.rpc.plugin_stop("bcli")
# Failure case of feerate is tested in test_misc.py # Failure case of feerate is tested in test_misc.py
assert "feerate" in l1.rpc.call("getfeerate", {"blocks": 3, estimates = l1.rpc.call("estimatefees")
"mode": "CONSERVATIVE"}) for est in ["opening", "mutual_close", "unilateral_close", "delayed_to_us",
"htlc_resolution", "penalty", "min_acceptable",
"max_acceptable"]:
assert est in estimates
resp = l1.rpc.call("getchaininfo") resp = l1.rpc.call("getchaininfo")
assert resp["chain"] == chainparams['name'] assert resp["chain"] == chainparams['name']