libplugin: update API to use json_out.

We now hand around struct json_out members, rather than using formatted
strings, so plugins need to construct them properly.

There's no automatic conversion between ' and " any more, so those
are eliminated too.  pay still uses some manual construction of elements.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell
2019-06-12 10:08:54 +09:30
parent c77e29c403
commit c585f22711
4 changed files with 269 additions and 231 deletions

View File

@@ -1,6 +1,7 @@
#include <ccan/array_size/array_size.h>
#include <ccan/cast/cast.h>
#include <ccan/intmap/intmap.h>
#include <ccan/json_out/json_out.h>
#include <ccan/tal/str/str.h>
#include <ccan/time/time.h>
#include <common/amount.h>
@@ -136,12 +137,38 @@ static void attempt_failed_tok(struct pay_command *pc, const char *method,
buf + msg->start);
else
attempt_failed_fmt(pc,
"{ 'message': 'Call to %s failed', %.*s",
"{ \"message\": \"Call to %s failed\", %.*s",
method,
errtok->end - errtok->start - 1,
buf + errtok->start + 1);
}
/* Helper to copy JSON object directly into a json_out */
static void json_out_add_raw_len(struct json_out *jout,
const char *fieldname,
const char *jsonstr, size_t len)
{
char *p;
p = json_out_member_direct(jout, fieldname, len);
memcpy(p, jsonstr, len);
}
static void json_out_add_raw(struct json_out *jout,
const char *fieldname,
const char *jsonstr)
{
json_out_add_raw_len(jout, fieldname, jsonstr, strlen(jsonstr));
}
/* Helper to add a u64. */
static void json_out_add_u64(struct json_out *jout,
const char *fieldname,
u64 val)
{
json_out_add(jout, fieldname, false, "%"PRIu64, val);
}
static struct command_result *start_pay_attempt(struct command *cmd,
struct pay_command *pc,
const char *fmt, ...);
@@ -178,24 +205,23 @@ static size_t count_sendpays(const struct pay_attempt *attempts)
static struct command_result *waitsendpay_expired(struct command *cmd,
struct pay_command *pc)
{
char *errmsg, *data;
char *errmsg;
struct json_out *data;
size_t num_attempts = count_sendpays(pc->ps->attempts);
errmsg = tal_fmt(pc, "Gave up after %zu attempt%s: see paystatus",
num_attempts, num_attempts == 1 ? "" : "s");
data = tal_strdup(pc, "{ 'attempts': [ ");
data = json_out_new(NULL);
json_out_start(data, NULL, '{');
json_out_start(data, "attempts", '[');
for (size_t i = 0; i < tal_count(pc->ps->attempts); i++) {
if (pc->ps->attempts[i].route)
tal_append_fmt(&data, "%s { 'route': %s,\n 'failure': %s\n }",
i == 0 ? "" : ",",
pc->ps->attempts[i].route,
pc->ps->attempts[i].failure);
else
tal_append_fmt(&data, "%s { 'failure': %s\n }",
i == 0 ? "" : ",",
pc->ps->attempts[i].failure);
json_out_add_raw(data, "route",
pc->ps->attempts[i].route);
json_out_add_raw(data, "failure", pc->ps->attempts[i].failure);
}
tal_append_fmt(&data, "] }");
json_out_end(data, ']');
json_out_end(data, '}');
return command_done_err(cmd, PAY_STOPPED_RETRYING, errmsg, data);
}
@@ -315,8 +341,8 @@ static struct command_result *sendpay_done(struct command *cmd,
{
return send_outreq(cmd, "waitsendpay",
waitsendpay_done, waitsendpay_error, pc,
"'payment_hash': '%s'",
pc->payment_hash);
take(json_out_obj(NULL, "payment_hash",
pc->payment_hash)));
}
/* Calculate how many millisatoshi we need at the start of this route
@@ -375,10 +401,10 @@ static const char *join_routehint(const tal_t *ctx,
return tal_free(ret);
tal_append_fmt(&ret, ", {"
" 'id': '%s',"
" 'channel': '%s',"
" 'msatoshi': '%s',"
" 'delay': %u }",
" \"id\": \"%s\","
" \"channel\": \"%s\","
" \"msatoshi\": \"%s\","
" \"delay\": %u }",
/* pubkey of *destination* */
route_pubkey(tmpctx, pc, routehint, i + 1),
type_to_string(tmpctx, struct short_channel_id,
@@ -455,10 +481,10 @@ static struct command_result *getroute_done(struct command *cmd,
{
struct pay_attempt *attempt = current_attempt(pc);
const jsmntok_t *t = json_get_member(buf, result, "route");
char *json_desc;
struct amount_msat fee;
u32 delay;
double feepercent;
struct json_out *params;
if (!t)
plugin_err("getroute gave no 'route'? '%.*s'",
@@ -469,7 +495,7 @@ static struct command_result *getroute_done(struct command *cmd,
pc, pc->current_routehint);
if (!attempt->route) {
attempt_failed_fmt(pc,
"{ 'message': 'Joining routehint gave absurd fee' }");
"{ \"message\": \"Joining routehint gave absurd fee\" }");
return next_routehint(cmd, pc);
}
} else
@@ -497,7 +523,7 @@ static struct command_result *getroute_done(struct command *cmd,
&& feepercent > pc->maxfeepercent) {
const jsmntok_t *charger;
attempt_failed_fmt(pc, "{ 'message': 'Route wanted fee of %s' }",
attempt_failed_fmt(pc, "{ \"message\": \"Route wanted fee of %s\" }",
type_to_string(tmpctx, struct amount_msat,
&fee));
@@ -526,7 +552,7 @@ static struct command_result *getroute_done(struct command *cmd,
const jsmntok_t *delayer;
attempt_failed_fmt(pc,
"{ 'message': 'Route wanted delay of %u blocks' }",
"{ \"message\": \"Route wanted delay of %u blocks\" }",
delay);
/* Remember this if we eliminating this causes us to have no
@@ -549,18 +575,18 @@ static struct command_result *getroute_done(struct command *cmd,
return next_routehint(cmd, pc);
}
if (pc->label)
json_desc = tal_fmt(pc, ", 'label': '%s'", pc->label);
else
json_desc = "";
attempt->sendpay = true;
params = json_out_new(NULL);
json_out_start(params, NULL, '{');
json_out_add_raw(params, "route", attempt->route);
json_out_add(params, "payment_hash", true, "%s", pc->payment_hash);
json_out_add(params, "bolt11", true, "%s", pc->ps->bolt11);
if (pc->label)
json_out_add(params, "label", true, "%s", pc->label);
json_out_end(params, '}');
return send_outreq(cmd, "sendpay", sendpay_done, sendpay_error, pc,
"'route': %s, 'payment_hash': '%s', 'bolt11': '%s'%s",
attempt->route,
pc->payment_hash,
pc->ps->bolt11,
json_desc);
take(params));
}
@@ -600,7 +626,6 @@ static struct command_result *start_pay_attempt(struct command *cmd,
struct pay_command *pc,
const char *fmt, ...)
{
char *exclude;
struct amount_msat msat;
const char *dest;
size_t max_hops = ROUTING_MAX_HOPS;
@@ -608,6 +633,7 @@ static struct command_result *start_pay_attempt(struct command *cmd,
struct pay_attempt *attempt;
va_list ap;
size_t n;
struct json_out *params;
n = tal_count(pc->ps->attempts);
tal_resize(&pc->ps->attempts, n+1);
@@ -627,17 +653,6 @@ static struct command_result *start_pay_attempt(struct command *cmd,
/* routehint set below. */
if (tal_count(pc->excludes) != 0) {
exclude = tal_strdup(tmpctx, ",'exclude': [");
for (size_t i = 0; i < tal_count(pc->excludes); i++)
/* JSON.org grammar doesn't allow trailing , */
tal_append_fmt(&exclude, "%s %s",
i == 0 ? "" : ",",
pc->excludes[i]);
tal_append_fmt(&exclude, "]");
} else
exclude = "";
/* If we have a routehint, try that first; we need to do extra
* checks that it meets our criteria though. */
if (pc->current_routehint) {
@@ -646,7 +661,7 @@ static struct command_result *start_pay_attempt(struct command *cmd,
attempt->routehint,
tal_count(attempt->routehint))) {
attempt_failed_fmt(pc,
"{ 'message': 'Routehint absurd fee' }");
"{ \"message\": \"Routehint absurd fee\" }");
return next_routehint(cmd, pc);
}
dest = type_to_string(tmpctx, struct node_id,
@@ -663,15 +678,24 @@ static struct command_result *start_pay_attempt(struct command *cmd,
}
/* OK, ask for route to destination */
params = json_out_new(NULL);
json_out_start(params, NULL, '{');
json_out_addstr(params, "id", dest);
json_out_addstr(params, "msatoshi",
type_to_string(tmpctx, struct amount_msat, &msat));
json_out_add_u64(params, "cltv", cltv);
json_out_add_u64(params, "maxhops", max_hops);
json_out_add(params, "riskfactor", false, "%f", pc->riskfactor);
if (tal_count(pc->excludes) != 0) {
json_out_start(params, "exclude", '[');
for (size_t i = 0; i < tal_count(pc->excludes); i++)
json_out_addstr(params, NULL, pc->excludes[i]);
json_out_end(params, ']');
}
json_out_end(params, '}');
return send_outreq(cmd, "getroute", getroute_done, getroute_error, pc,
"'id': '%s',"
"'msatoshi': '%s',"
"'cltv': %u,"
"'maxhops': %zu,"
"'riskfactor': %f%s",
dest,
type_to_string(tmpctx, struct amount_msat, &msat),
cltv, max_hops, pc->riskfactor, exclude);
take(params));
}
/* BOLT #7:
@@ -751,7 +775,7 @@ static struct command_result *shadow_route(struct command *cmd,
return send_outreq(cmd, "listchannels",
add_shadow_route, forward_error, pc,
"'source' : '%s'", pc->shadow_dest);
take(json_out_obj(NULL, "source", pc->shadow_dest)));
}
/* gossipd doesn't know much about the current state of channels; here we
@@ -996,10 +1020,9 @@ static struct command_result *json_pay(struct command *cmd,
pc->routehints = filter_routehints(pc, b11->routes);
pc->expensive_route = NULL;
/* Get capacities of local channels. */
/* Get capacities of local channels (no parameters) */
return send_outreq(cmd, "listpeers", listpeers_done, forward_error, pc,
/* gcc doesn't like zero-length format strings! */
" ");
take(json_out_obj(NULL, NULL, NULL)));
}
/* FIXME: Add this to ccan/time? */
@@ -1014,7 +1037,7 @@ static void utc_timestring(const struct timeabs *time, char str[UTC_TIMELEN])
(int) time->ts.tv_nsec / 1000000);
}
static void add_attempt(char **ret,
static void add_attempt(struct json_out *ret,
const struct pay_status *ps,
const struct pay_attempt *attempt)
{
@@ -1022,62 +1045,56 @@ static void add_attempt(char **ret,
utc_timestring(&attempt->start, timestr);
tal_append_fmt(ret, "{ 'strategy': '%s',"
" 'start_time': '%s',"
" 'age_in_seconds': %"PRIu64,
attempt->why,
timestr,
time_to_sec(time_between(time_now(), attempt->start)));
json_out_start(ret, NULL, '{');
json_out_addstr(ret, "strategy", attempt->why);
json_out_addstr(ret, "start_time", timestr);
json_out_add_u64(ret, "age_in_seconds",
time_to_sec(time_between(time_now(), attempt->start)));
if (attempt->result || attempt->failure) {
utc_timestring(&attempt->end, timestr);
tal_append_fmt(ret, ", 'end_time': '%s'"
", 'duration_in_seconds': %"PRIu64,
timestr,
time_to_sec(time_between(attempt->end,
attempt->start)));
json_out_addstr(ret, "end_time", timestr);
json_out_add_u64(ret, "duration_in_seconds",
time_to_sec(time_between(attempt->end,
attempt->start)));
}
if (tal_count(attempt->routehint)) {
tal_append_fmt(ret, ", 'routehint': [");
json_out_start(ret, "routehint", '[');
for (size_t i = 0; i < tal_count(attempt->routehint); i++) {
tal_append_fmt(ret, "%s{"
" 'id': '%s',"
" 'channel': '%s',"
" 'fee_base_msat': %u,"
" 'fee_proportional_millionths': %u,"
" 'cltv_expiry_delta': %u }",
i == 0 ? "" : ", ",
type_to_string(tmpctx, struct node_id,
&attempt->routehint[i].pubkey),
type_to_string(tmpctx,
struct short_channel_id,
&attempt->routehint[i].short_channel_id),
attempt->routehint[i].fee_base_msat,
attempt->routehint[i].fee_proportional_millionths,
attempt->routehint[i].cltv_expiry_delta);
json_out_start(ret, NULL, '{');
json_out_addstr(ret, "id",
type_to_string(tmpctx, struct node_id,
&attempt->routehint[i].pubkey));
json_out_addstr(ret, "channel",
type_to_string(tmpctx,
struct short_channel_id,
&attempt->routehint[i].short_channel_id));
json_out_add_u64(ret, "fee_base_msat",
attempt->routehint[i].fee_base_msat);
json_out_add_u64(ret, "fee_proportional_millionths",
attempt->routehint[i].fee_proportional_millionths);
json_out_add_u64(ret, "cltv_expiry_delta",
attempt->routehint[i].cltv_expiry_delta);
json_out_end(ret, '}');
}
tal_append_fmt(ret, "]");
json_out_end(ret, ']');
}
if (tal_count(attempt->excludes)) {
for (size_t i = 0; i < tal_count(attempt->excludes); i++) {
if (i == 0)
tal_append_fmt(ret, ", 'excluded_channels': [");
else
tal_append_fmt(ret, ", ");
tal_append_fmt(ret, "'%s'", attempt->excludes[i]);
}
tal_append_fmt(ret, "]");
json_out_start(ret, "excluded_channels", '[');
for (size_t i = 0; i < tal_count(attempt->excludes); i++)
json_out_addstr(ret, NULL, attempt->excludes[i]);
json_out_end(ret, ']');
}
if (attempt->route)
tal_append_fmt(ret, ", 'route': %s", attempt->route);
json_out_add_raw(ret, "route", attempt->route);
if (attempt->failure)
tal_append_fmt(ret, ", 'failure': %s", attempt->failure);
json_out_add_raw(ret, "failure", attempt->failure);
if (attempt->result)
tal_append_fmt(ret, ", 'success': %s", attempt->result);
json_out_add_raw(ret, "success", attempt->result);
tal_append_fmt(ret, "}");
json_out_end(ret, '}');
}
static struct command_result *json_paystatus(struct command *cmd,
@@ -1086,71 +1103,65 @@ static struct command_result *json_paystatus(struct command *cmd,
{
struct pay_status *ps;
const char *b11str;
char *ret;
bool some = false;
struct json_out *ret;
if (!param(cmd, buf, params,
p_opt("bolt11", param_string, &b11str),
NULL))
return NULL;
ret = tal_fmt(cmd, "{ 'pay': [");
ret = json_out_new(NULL);
json_out_start(ret, NULL, '{');
json_out_start(ret, "pay", '[');
/* FIXME: Index by bolt11 string! */
list_for_each(&pay_status, ps, list) {
if (b11str && !streq(b11str, ps->bolt11))
continue;
if (some)
tal_append_fmt(&ret, ",\n");
some = true;
tal_append_fmt(&ret, "{ 'bolt11': '%s',"
" 'msatoshi': %"PRIu64", "
" 'amount_msat': '%s', "
" 'destination': '%s'",
ps->bolt11,
ps->msat.millisatoshis, /* Raw: JSON */
json_out_start(ret, NULL, '{');
json_out_addstr(ret, "bolt11", ps->bolt11);
json_out_add_u64(ret, "msatoshi",
ps->msat.millisatoshis); /* Raw: JSON */
json_out_addstr(ret, "amount_msat",
type_to_string(tmpctx, struct amount_msat,
&ps->msat), ps->dest);
&ps->msat));
json_out_addstr(ret, "destination", ps->dest);
if (ps->label)
tal_append_fmt(&ret, ", 'label': '%s'", ps->label);
json_out_addstr(ret, "label", ps->label);
if (ps->routehint_modifications)
tal_append_fmt(&ret, ", 'routehint_modifications': '%s'",
ps->routehint_modifications);
json_out_addstr(ret, "routehint_modifications",
ps->routehint_modifications);
if (ps->shadow && !streq(ps->shadow, ""))
tal_append_fmt(&ret, ", 'shadow': '%s'", ps->shadow);
json_out_addstr(ret, "shadow", ps->shadow);
if (ps->exclusions)
tal_append_fmt(&ret, ", 'local_exclusions': '%s'",
ps->exclusions);
json_out_addstr(ret, "local_exclusions", ps->exclusions);
assert(tal_count(ps->attempts));
for (size_t i = 0; i < tal_count(ps->attempts); i++) {
if (i == 0)
tal_append_fmt(&ret, ", 'attempts': [");
else
tal_append_fmt(&ret, ",");
add_attempt(&ret, ps, &ps->attempts[i]);
}
tal_append_fmt(&ret, "] }");
json_out_start(ret, "attempts", '[');
for (size_t i = 0; i < tal_count(ps->attempts); i++)
add_attempt(ret, ps, &ps->attempts[i]);
json_out_end(ret, ']');
json_out_end(ret, '}');
}
tal_append_fmt(&ret, "] }");
json_out_end(ret, ']');
json_out_end(ret, '}');
return command_success(cmd, ret);
}
static const jsmntok_t *copy_member(char **ret,
const char *buf,
const jsmntok_t *result,
const char *membername,
const char *term)
/* Copy field and member to output, if it exists: return member */
static const jsmntok_t *copy_member(struct json_out *ret,
const char *buf, const jsmntok_t *obj,
const char *membername)
{
const jsmntok_t *m = json_get_member(buf, result, membername);
if (m)
tal_append_fmt(ret, "'%s': %.*s%s",
membername,
json_tok_full_len(m), json_tok_full(buf, m),
term);
const jsmntok_t *m = json_get_member(buf, obj, membername);
if (!m)
return NULL;
/* Literal copy: it's already JSON escaped, and may be a string. */
json_out_add_raw_len(ret, membername,
json_tok_full(buf, m), json_tok_full_len(m));
return m;
}
@@ -1175,33 +1186,30 @@ static struct command_result *listsendpays_done(struct command *cmd,
{
size_t i;
const jsmntok_t *t, *arr;
char *ret;
bool some = false;
struct json_out *ret;
arr = json_get_member(buf, result, "payments");
if (!arr || arr->type != JSMN_ARRAY)
return command_fail(cmd, LIGHTNINGD,
"Unexpected non-array result from listsendpays");
ret = tal_fmt(cmd, "{ 'pays': [");
ret = json_out_new(NULL);
json_out_start(ret, NULL, '{');
json_out_start(ret, "pays", '[');
json_for_each_arr(i, t, arr) {
const jsmntok_t *status, *b11;
if (some)
tal_append_fmt(&ret, ",\n");
some = true;
tal_append_fmt(&ret, "{");
json_out_start(ret, NULL, '{');
/* Old payments didn't have bolt11 field */
b11 = copy_member(&ret, buf, t, "bolt11", ",");
b11 = copy_member(ret, buf, t, "bolt11");
if (!b11) {
if (b11str) {
/* If it's a single query, we can fake it */
tal_append_fmt(&ret, "'bolt11': '%s',", b11str);
json_out_addstr(ret, "bolt11", b11str);
} else {
copy_member(&ret, buf, t, "payment_hash", ",");
copy_member(&ret, buf, t, "destination", ",");
copy_member(&ret, buf, t, "amount_msat", ",");
copy_member(ret, buf, t, "payment_hash");
copy_member(ret, buf, t, "destination");
copy_member(ret, buf, t, "amount_msat");
}
}
@@ -1210,19 +1218,20 @@ static struct command_result *listsendpays_done(struct command *cmd,
if (status) {
if (json_tok_streq(buf, status, "failed")
&& attempt_ongoing(buf, b11)) {
tal_append_fmt(&ret, "'status': 'pending',");
json_out_addstr(ret, "status", "pending");
} else {
copy_member(&ret, buf, t, "status", ",");
copy_member(ret, buf, t, "status");
if (json_tok_streq(buf, status, "complete"))
copy_member(&ret, buf, t,
"payment_preimage", ",");
copy_member(ret, buf, t,
"payment_preimage");
}
}
copy_member(&ret, buf, t, "label", ",");
copy_member(&ret, buf, t, "amount_sent_msat", "");
tal_append_fmt(&ret, "}");
copy_member(ret, buf, t, "label");
copy_member(ret, buf, t, "amount_sent_msat");
json_out_end(ret, '}');
}
tal_append_fmt(&ret, "] }");
json_out_end(ret, ']');
json_out_end(ret, '}');
return command_success(cmd, ret);
}
@@ -1230,7 +1239,7 @@ static struct command_result *json_listpays(struct command *cmd,
const char *buf,
const jsmntok_t *params)
{
const char *b11str, *paramstr;
const char *b11str;
/* FIXME: would be nice to parse as a bolt11 so check worked in future */
if (!param(cmd, buf, params,
@@ -1238,26 +1247,25 @@ static struct command_result *json_listpays(struct command *cmd,
NULL))
return NULL;
if (b11str)
paramstr = tal_fmt(tmpctx, "'bolt11' : '%s'", b11str);
else
paramstr = "";
return send_outreq(cmd, "listsendpays",
listsendpays_done, forward_error,
cast_const(char *, b11str),
"%s", paramstr);
/* Neatly returns empty object if b11str is NULL */
take(json_out_obj(NULL, "bolt11", b11str)));
}
static void init(struct plugin_conn *rpc)
{
const char *field;
field = rpc_delve(tmpctx, "getinfo", "", rpc, ".id");
field = rpc_delve(tmpctx, "getinfo",
take(json_out_obj(NULL, NULL, NULL)), rpc, ".id");
if (!node_id_from_hexstr(field, strlen(field), &my_id))
plugin_err("getinfo didn't contain valid id: '%s'", field);
field = rpc_delve(tmpctx, "listconfigs",
"'config': 'max-locktime-blocks'",
take(json_out_obj(NULL,
"config", "max-locktime-blocks")),
rpc, ".max-locktime-blocks");
maxdelay_default = atoi(field);
}