Browse Source

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>
pull/2938/head
Rusty Russell 6 years ago
parent
commit
c585f22711
  1. 18
      plugins/autoclean.c
  2. 100
      plugins/libplugin.c
  3. 34
      plugins/libplugin.h
  4. 326
      plugins/pay.c

18
plugins/autoclean.c

@ -1,4 +1,5 @@
#include <ccan/array_size/array_size.h> #include <ccan/array_size/array_size.h>
#include <ccan/json_out/json_out.h>
#include <ccan/tal/str/str.h> #include <ccan/tal/str/str.h>
#include <ccan/time/time.h> #include <ccan/time/time.h>
#include <common/utils.h> #include <common/utils.h>
@ -22,10 +23,16 @@ static struct command_result *ignore(struct command *timer,
static struct command_result *do_clean(void) static struct command_result *do_clean(void)
{ {
u64 age = time_now().ts.tv_sec - expired_by; struct json_out *params = json_out_new(NULL);
json_out_start(params, NULL, '{');
json_out_add(params, "maxexpirytime", false, "%"PRIu64,
time_now().ts.tv_sec - expired_by);
json_out_end(params, '}');
json_out_finished(params);
/* FIXME: delexpiredinvoice should be in our plugin too! */ /* FIXME: delexpiredinvoice should be in our plugin too! */
return send_outreq(NULL, "delexpiredinvoice", ignore, ignore, NULL, return send_outreq(NULL, "delexpiredinvoice", ignore, ignore, NULL,
"'maxexpirytime': %"PRIu64, age); take(params));
} }
static struct command_result *json_autocleaninvoice(struct command *cmd, static struct command_result *json_autocleaninvoice(struct command *cmd,
@ -46,14 +53,15 @@ static struct command_result *json_autocleaninvoice(struct command *cmd,
if (cycle_seconds == 0) { if (cycle_seconds == 0) {
tal_free(cleantimer); tal_free(cleantimer);
return command_success(cmd, "'Autoclean timer disabled'"); return command_success_str(cmd, "Autoclean timer disabled");
} }
tal_free(cleantimer); tal_free(cleantimer);
cleantimer = plugin_timer(rpc, time_from_sec(cycle_seconds), do_clean); cleantimer = plugin_timer(rpc, time_from_sec(cycle_seconds), do_clean);
return command_success(cmd, tal_fmt(cmd, "'Autocleaning %"PRIu64 return command_success_str(cmd,
tal_fmt(cmd, "Autocleaning %"PRIu64
"-second old invoices every %"PRIu64 "-second old invoices every %"PRIu64
" seconds'", " seconds",
expired_by, cycle_seconds)); expired_by, cycle_seconds));
} }

100
plugins/libplugin.c

@ -84,6 +84,20 @@ struct command_result *command_param_failed(void)
return &complete; return &complete;
} }
struct json_out *json_out_obj(const tal_t *ctx,
const char *fieldname,
const char *str)
{
struct json_out *jout = json_out_new(ctx);
json_out_start(jout, NULL, '{');
if (str)
json_out_addstr(jout, fieldname, str);
json_out_end(jout, '}');
json_out_finished(jout);
return jout;
}
/* Realloc helper for tal membufs */ /* Realloc helper for tal membufs */
static void *membuf_tal_realloc(struct membuf *mb, void *rawelems, static void *membuf_tal_realloc(struct membuf *mb, void *rawelems,
size_t newsize) size_t newsize)
@ -190,17 +204,7 @@ static struct command_result *WARN_UNUSED_RESULT end_cmd(struct command *cmd)
return &complete; return &complete;
} }
/* FIXME: We promised callers we'd turn ' into ". */ /* str is raw JSON from RPC output. */
static void copy_with_quote_sub(char *dst, const char *src, size_t len)
{
for (size_t i = 0; i < len; i++) {
if (src[i] == '\'')
dst[i] = '"';
else
dst[i] = src[i];
}
}
static struct command_result *WARN_UNUSED_RESULT static struct command_result *WARN_UNUSED_RESULT
command_done_raw(struct command *cmd, command_done_raw(struct command *cmd,
const char *label, const char *label,
@ -208,23 +212,42 @@ command_done_raw(struct command *cmd,
{ {
struct json_out *jout = start_json_rpc(cmd, cmd->id); struct json_out *jout = start_json_rpc(cmd, cmd->id);
copy_with_quote_sub(json_out_member_direct(jout, label, size), memcpy(json_out_member_direct(jout, label, size), str, size);
str, size);
finish_and_send_json(STDOUT_FILENO, jout); finish_and_send_json(STDOUT_FILENO, jout);
return end_cmd(cmd); return end_cmd(cmd);
} }
static struct command_result *WARN_UNUSED_RESULT struct command_result *WARN_UNUSED_RESULT
command_done_ok(struct command *cmd, const char *result) command_success(struct command *cmd, const struct json_out *result)
{ {
return command_done_raw(cmd, "result", result, strlen(result)); struct json_out *jout = start_json_rpc(cmd, cmd->id);
json_out_add_splice(jout, "result", result);
finish_and_send_json(STDOUT_FILENO, jout);
return end_cmd(cmd);
}
struct command_result *WARN_UNUSED_RESULT
command_success_str(struct command *cmd, const char *str)
{
struct json_out *jout = start_json_rpc(cmd, cmd->id);
if (str)
json_out_addstr(jout, "result", str);
else {
/* Use an empty object if they don't want anything. */
json_out_start(jout, "result", '{');
json_out_end(jout, '}');
}
finish_and_send_json(STDOUT_FILENO, jout);
return end_cmd(cmd);
} }
struct command_result *command_done_err(struct command *cmd, struct command_result *command_done_err(struct command *cmd,
int code, int code,
const char *errmsg, const char *errmsg,
const char *data) const struct json_out *data)
{ {
struct json_out *jout = start_json_rpc(cmd, cmd->id); struct json_out *jout = start_json_rpc(cmd, cmd->id);
@ -232,11 +255,8 @@ struct command_result *command_done_err(struct command *cmd,
json_out_add(jout, "code", false, "%d", code); json_out_add(jout, "code", false, "%d", code);
json_out_addstr(jout, "message", errmsg); json_out_addstr(jout, "message", errmsg);
if (data) { if (data)
char *p; json_out_add_splice(jout, "data", data);
p = json_out_member_direct(jout, "data", strlen(data));
copy_with_quote_sub(p, data, strlen(data));
}
json_out_end(jout, '}'); json_out_end(jout, '}');
finish_and_send_json(STDOUT_FILENO, jout); finish_and_send_json(STDOUT_FILENO, jout);
@ -250,11 +270,6 @@ struct command_result *timer_complete(void)
return &complete; return &complete;
} }
struct command_result *command_success(struct command *cmd, const char *result)
{
return command_done_raw(cmd, "result", result, strlen(result));
}
struct command_result *forward_error(struct command *cmd, struct command_result *forward_error(struct command *cmd,
const char *buf, const char *buf,
const jsmntok_t *error, const jsmntok_t *error,
@ -342,23 +357,23 @@ static const jsmntok_t *read_rpc_reply(const tal_t *ctx,
static struct json_out *start_json_request(const tal_t *ctx, static struct json_out *start_json_request(const tal_t *ctx,
u64 id, u64 id,
const char *method, const char *method,
const char *params) const struct json_out *params TAKES)
{ {
struct json_out *jout; struct json_out *jout;
jout = start_json_rpc(tmpctx, id); jout = start_json_rpc(tmpctx, id);
json_out_addstr(jout, "method", method); json_out_addstr(jout, "method", method);
json_out_start(jout, "params", '{'); json_out_add_splice(jout, "params", params);
copy_with_quote_sub(json_out_direct(jout, strlen(params)), if (taken(params))
params, strlen(params)); tal_free(params);
json_out_end(jout, '}');
return jout; return jout;
} }
/* Synchronous routine to send command and extract single field from response */ /* Synchronous routine to send command and extract single field from response */
const char *rpc_delve(const tal_t *ctx, const char *rpc_delve(const tal_t *ctx,
const char *method, const char *params, const char *method,
const struct json_out *params TAKES,
struct plugin_conn *rpc, const char *guide) struct plugin_conn *rpc, const char *guide)
{ {
bool error; bool error;
@ -435,12 +450,10 @@ send_outreq_(struct command *cmd,
const jsmntok_t *result, const jsmntok_t *result,
void *arg), void *arg),
void *arg, void *arg,
const char *paramfmt_single_ticks, ...) const struct json_out *params TAKES)
{ {
va_list ap;
struct json_out *jout; struct json_out *jout;
struct out_req *out; struct out_req *out;
char *params;
out = tal(cmd, struct out_req); out = tal(cmd, struct out_req);
out->id = next_outreq_id++; out->id = next_outreq_id++;
@ -450,10 +463,6 @@ send_outreq_(struct command *cmd,
out->arg = arg; out->arg = arg;
uintmap_add(&out_reqs, out->id, out); uintmap_add(&out_reqs, out->id, out);
va_start(ap, paramfmt_single_ticks);
params = tal_vfmt(tmpctx, paramfmt_single_ticks, ap);
va_end(ap);
jout = start_json_request(tmpctx, out->id, method, params); jout = start_json_request(tmpctx, out->id, method, params);
finish_and_send_json(rpc_conn.fd, jout); finish_and_send_json(rpc_conn.fd, jout);
@ -467,8 +476,6 @@ handle_getmanifest(struct command *getmanifest_cmd,
const struct plugin_option *opts) const struct plugin_option *opts)
{ {
struct json_out *params = json_out_new(tmpctx); struct json_out *params = json_out_new(tmpctx);
size_t len;
const char *p;
json_out_start(params, NULL, '{'); json_out_start(params, NULL, '{');
json_out_start(params, "options", '['); json_out_start(params, "options", '[');
@ -497,8 +504,7 @@ handle_getmanifest(struct command *getmanifest_cmd,
json_out_end(params, '}'); json_out_end(params, '}');
json_out_finished(params); json_out_finished(params);
p = json_out_contents(params, &len); return command_success(getmanifest_cmd, params);
return command_done_raw(getmanifest_cmd, "result", p, len);
} }
static struct command_result *handle_init(struct command *init_cmd, static struct command_result *handle_init(struct command *init_cmd,
@ -511,6 +517,7 @@ static struct command_result *handle_init(struct command *init_cmd,
struct sockaddr_un addr; struct sockaddr_un addr;
size_t i; size_t i;
char *dir; char *dir;
struct json_out *param_obj;
/* Move into lightning directory: other files are relative */ /* Move into lightning directory: other files are relative */
dirtok = json_delve(buf, params, ".configuration.lightning-dir"); dirtok = json_delve(buf, params, ".configuration.lightning-dir");
@ -533,8 +540,9 @@ static struct command_result *handle_init(struct command *init_cmd,
rpctok->end - rpctok->start, buf + rpctok->start, rpctok->end - rpctok->start, buf + rpctok->start,
strerror(errno)); strerror(errno));
param_obj = json_out_obj(NULL, "config", "allow-deprecated-apis");
deprecated_apis = streq(rpc_delve(tmpctx, "listconfigs", deprecated_apis = streq(rpc_delve(tmpctx, "listconfigs",
"'config': 'allow-deprecated-apis'", take(param_obj),
&rpc_conn, &rpc_conn,
".allow-deprecated-apis"), ".allow-deprecated-apis"),
"true"); "true");
@ -558,7 +566,7 @@ static struct command_result *handle_init(struct command *init_cmd,
if (init) if (init)
init(&rpc_conn); init(&rpc_conn);
return command_done_ok(init_cmd, "{}"); return command_success_str(init_cmd, NULL);
} }
char *u64_option(const char *arg, u64 *i) char *u64_option(const char *arg, u64 *i)

34
plugins/libplugin.h

@ -11,6 +11,7 @@
#include <common/status_levels.h> #include <common/status_levels.h>
struct command; struct command;
struct json_out;
struct plugin_conn; struct plugin_conn;
extern bool deprecated_apis; extern bool deprecated_apis;
@ -35,6 +36,12 @@ struct plugin_option {
void *arg; void *arg;
}; };
/* Helper to create a zero or single-value JSON object; if @str is NULL,
* object is empty. */
struct json_out *json_out_obj(const tal_t *ctx,
const char *fieldname,
const char *str);
/* Return this iff the param() call failed in your handler. */ /* Return this iff the param() call failed in your handler. */
struct command_result *command_param_failed(void); struct command_result *command_param_failed(void);
@ -42,27 +49,34 @@ struct command_result *command_param_failed(void);
void NORETURN plugin_err(const char *fmt, ...); void NORETURN plugin_err(const char *fmt, ...);
/* This command is finished, here's a detailed error; @cmd cannot be /* This command is finished, here's a detailed error; @cmd cannot be
* NULL, data can be NULL. */ * NULL, data can be NULL; otherwise it must be a JSON object. */
struct command_result *WARN_UNUSED_RESULT struct command_result *WARN_UNUSED_RESULT
command_done_err(struct command *cmd, command_done_err(struct command *cmd,
int code, int code,
const char *errmsg, const char *errmsg,
const char *data); const struct json_out *data);
/* This command is finished, here's the result object; @cmd cannot be NULL. */
struct command_result *WARN_UNUSED_RESULT
command_success(struct command *cmd, const struct json_out *result);
/* This command is finished, here's the success msg; @cmd cannot be NULL. */ /* Simple version where we just want to send a string, or NULL means an empty
* result object. @cmd cannot be NULL. */
struct command_result *WARN_UNUSED_RESULT struct command_result *WARN_UNUSED_RESULT
command_success(struct command *cmd, const char *result); command_success_str(struct command *cmd, const char *str);
/* Synchronous helper to send command and extract single field from /* Synchronous helper to send command and extract single field from
* response; can only be used in init callback. */ * response; can only be used in init callback. */
const char *rpc_delve(const tal_t *ctx, const char *rpc_delve(const tal_t *ctx,
const char *method, const char *params, const char *method,
const struct json_out *params TAKES,
struct plugin_conn *rpc, const char *guide); struct plugin_conn *rpc, const char *guide);
/* Async rpc request. For convenience, and single ' are turned into ". /* Async rpc request.
* @cmd can be NULL if we're coming from a timer callback. * @cmd can be NULL if we're coming from a timer callback.
* @params can be NULL, otherwise it's an array or object.
*/ */
PRINTF_FMT(6,7) struct command_result * struct command_result *
send_outreq_(struct command *cmd, send_outreq_(struct command *cmd,
const char *method, const char *method,
struct command_result *(*cb)(struct command *command, struct command_result *(*cb)(struct command *command,
@ -74,9 +88,9 @@ send_outreq_(struct command *cmd,
const jsmntok_t *result, const jsmntok_t *result,
void *arg), void *arg),
void *arg, void *arg,
const char *paramfmt_single_ticks, ...); const struct json_out *params TAKES);
#define send_outreq(cmd, method, cb, errcb, arg, ...) \ #define send_outreq(cmd, method, cb, errcb, arg, params) \
send_outreq_((cmd), (method), \ send_outreq_((cmd), (method), \
typesafe_cb_preargs(struct command_result *, void *, \ typesafe_cb_preargs(struct command_result *, void *, \
(cb), (arg), \ (cb), (arg), \
@ -88,7 +102,7 @@ send_outreq_(struct command *cmd,
struct command *command, \ struct command *command, \
const char *buf, \ const char *buf, \
const jsmntok_t *result), \ const jsmntok_t *result), \
(arg), __VA_ARGS__) (arg), (params))
/* Callback to just forward error and close request; @cmd cannot be NULL */ /* Callback to just forward error and close request; @cmd cannot be NULL */
struct command_result *forward_error(struct command *cmd, struct command_result *forward_error(struct command *cmd,

326
plugins/pay.c

@ -1,6 +1,7 @@
#include <ccan/array_size/array_size.h> #include <ccan/array_size/array_size.h>
#include <ccan/cast/cast.h> #include <ccan/cast/cast.h>
#include <ccan/intmap/intmap.h> #include <ccan/intmap/intmap.h>
#include <ccan/json_out/json_out.h>
#include <ccan/tal/str/str.h> #include <ccan/tal/str/str.h>
#include <ccan/time/time.h> #include <ccan/time/time.h>
#include <common/amount.h> #include <common/amount.h>
@ -136,12 +137,38 @@ static void attempt_failed_tok(struct pay_command *pc, const char *method,
buf + msg->start); buf + msg->start);
else else
attempt_failed_fmt(pc, attempt_failed_fmt(pc,
"{ 'message': 'Call to %s failed', %.*s", "{ \"message\": \"Call to %s failed\", %.*s",
method, method,
errtok->end - errtok->start - 1, errtok->end - errtok->start - 1,
buf + 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, static struct command_result *start_pay_attempt(struct command *cmd,
struct pay_command *pc, struct pay_command *pc,
const char *fmt, ...); 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, static struct command_result *waitsendpay_expired(struct command *cmd,
struct pay_command *pc) struct pay_command *pc)
{ {
char *errmsg, *data; char *errmsg;
struct json_out *data;
size_t num_attempts = count_sendpays(pc->ps->attempts); size_t num_attempts = count_sendpays(pc->ps->attempts);
errmsg = tal_fmt(pc, "Gave up after %zu attempt%s: see paystatus", errmsg = tal_fmt(pc, "Gave up after %zu attempt%s: see paystatus",
num_attempts, num_attempts == 1 ? "" : "s"); 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++) { for (size_t i = 0; i < tal_count(pc->ps->attempts); i++) {
if (pc->ps->attempts[i].route) if (pc->ps->attempts[i].route)
tal_append_fmt(&data, "%s { 'route': %s,\n 'failure': %s\n }", json_out_add_raw(data, "route",
i == 0 ? "" : ",", pc->ps->attempts[i].route);
pc->ps->attempts[i].route, json_out_add_raw(data, "failure", pc->ps->attempts[i].failure);
pc->ps->attempts[i].failure);
else
tal_append_fmt(&data, "%s { 'failure': %s\n }",
i == 0 ? "" : ",",
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); 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", return send_outreq(cmd, "waitsendpay",
waitsendpay_done, waitsendpay_error, pc, waitsendpay_done, waitsendpay_error, pc,
"'payment_hash': '%s'", take(json_out_obj(NULL, "payment_hash",
pc->payment_hash); pc->payment_hash)));
} }
/* Calculate how many millisatoshi we need at the start of this route /* 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); return tal_free(ret);
tal_append_fmt(&ret, ", {" tal_append_fmt(&ret, ", {"
" 'id': '%s'," " \"id\": \"%s\","
" 'channel': '%s'," " \"channel\": \"%s\","
" 'msatoshi': '%s'," " \"msatoshi\": \"%s\","
" 'delay': %u }", " \"delay\": %u }",
/* pubkey of *destination* */ /* pubkey of *destination* */
route_pubkey(tmpctx, pc, routehint, i + 1), route_pubkey(tmpctx, pc, routehint, i + 1),
type_to_string(tmpctx, struct short_channel_id, 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); struct pay_attempt *attempt = current_attempt(pc);
const jsmntok_t *t = json_get_member(buf, result, "route"); const jsmntok_t *t = json_get_member(buf, result, "route");
char *json_desc;
struct amount_msat fee; struct amount_msat fee;
u32 delay; u32 delay;
double feepercent; double feepercent;
struct json_out *params;
if (!t) if (!t)
plugin_err("getroute gave no 'route'? '%.*s'", plugin_err("getroute gave no 'route'? '%.*s'",
@ -469,7 +495,7 @@ static struct command_result *getroute_done(struct command *cmd,
pc, pc->current_routehint); pc, pc->current_routehint);
if (!attempt->route) { if (!attempt->route) {
attempt_failed_fmt(pc, attempt_failed_fmt(pc,
"{ 'message': 'Joining routehint gave absurd fee' }"); "{ \"message\": \"Joining routehint gave absurd fee\" }");
return next_routehint(cmd, pc); return next_routehint(cmd, pc);
} }
} else } else
@ -497,7 +523,7 @@ static struct command_result *getroute_done(struct command *cmd,
&& feepercent > pc->maxfeepercent) { && feepercent > pc->maxfeepercent) {
const jsmntok_t *charger; 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, type_to_string(tmpctx, struct amount_msat,
&fee)); &fee));
@ -526,7 +552,7 @@ static struct command_result *getroute_done(struct command *cmd,
const jsmntok_t *delayer; const jsmntok_t *delayer;
attempt_failed_fmt(pc, attempt_failed_fmt(pc,
"{ 'message': 'Route wanted delay of %u blocks' }", "{ \"message\": \"Route wanted delay of %u blocks\" }",
delay); delay);
/* Remember this if we eliminating this causes us to have no /* 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); return next_routehint(cmd, pc);
} }
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) if (pc->label)
json_desc = tal_fmt(pc, ", 'label': '%s'", pc->label); json_out_add(params, "label", true, "%s", pc->label);
else json_out_end(params, '}');
json_desc = "";
attempt->sendpay = true;
return send_outreq(cmd, "sendpay", sendpay_done, sendpay_error, pc, return send_outreq(cmd, "sendpay", sendpay_done, sendpay_error, pc,
"'route': %s, 'payment_hash': '%s', 'bolt11': '%s'%s", take(params));
attempt->route,
pc->payment_hash,
pc->ps->bolt11,
json_desc);
} }
@ -600,7 +626,6 @@ static struct command_result *start_pay_attempt(struct command *cmd,
struct pay_command *pc, struct pay_command *pc,
const char *fmt, ...) const char *fmt, ...)
{ {
char *exclude;
struct amount_msat msat; struct amount_msat msat;
const char *dest; const char *dest;
size_t max_hops = ROUTING_MAX_HOPS; 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; struct pay_attempt *attempt;
va_list ap; va_list ap;
size_t n; size_t n;
struct json_out *params;
n = tal_count(pc->ps->attempts); n = tal_count(pc->ps->attempts);
tal_resize(&pc->ps->attempts, n+1); tal_resize(&pc->ps->attempts, n+1);
@ -627,17 +653,6 @@ static struct command_result *start_pay_attempt(struct command *cmd,
/* routehint set below. */ /* 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 /* If we have a routehint, try that first; we need to do extra
* checks that it meets our criteria though. */ * checks that it meets our criteria though. */
if (pc->current_routehint) { if (pc->current_routehint) {
@ -646,7 +661,7 @@ static struct command_result *start_pay_attempt(struct command *cmd,
attempt->routehint, attempt->routehint,
tal_count(attempt->routehint))) { tal_count(attempt->routehint))) {
attempt_failed_fmt(pc, attempt_failed_fmt(pc,
"{ 'message': 'Routehint absurd fee' }"); "{ \"message\": \"Routehint absurd fee\" }");
return next_routehint(cmd, pc); return next_routehint(cmd, pc);
} }
dest = type_to_string(tmpctx, struct node_id, 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 */ /* 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, return send_outreq(cmd, "getroute", getroute_done, getroute_error, pc,
"'id': '%s'," take(params));
"'msatoshi': '%s',"
"'cltv': %u,"
"'maxhops': %zu,"
"'riskfactor': %f%s",
dest,
type_to_string(tmpctx, struct amount_msat, &msat),
cltv, max_hops, pc->riskfactor, exclude);
} }
/* BOLT #7: /* BOLT #7:
@ -751,7 +775,7 @@ static struct command_result *shadow_route(struct command *cmd,
return send_outreq(cmd, "listchannels", return send_outreq(cmd, "listchannels",
add_shadow_route, forward_error, pc, 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 /* 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->routehints = filter_routehints(pc, b11->routes);
pc->expensive_route = NULL; 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, 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? */ /* 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); (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_status *ps,
const struct pay_attempt *attempt) const struct pay_attempt *attempt)
{ {
@ -1022,62 +1045,56 @@ static void add_attempt(char **ret,
utc_timestring(&attempt->start, timestr); utc_timestring(&attempt->start, timestr);
tal_append_fmt(ret, "{ 'strategy': '%s'," json_out_start(ret, NULL, '{');
" 'start_time': '%s'," json_out_addstr(ret, "strategy", attempt->why);
" 'age_in_seconds': %"PRIu64, json_out_addstr(ret, "start_time", timestr);
attempt->why, json_out_add_u64(ret, "age_in_seconds",
timestr,
time_to_sec(time_between(time_now(), attempt->start))); time_to_sec(time_between(time_now(), attempt->start)));
if (attempt->result || attempt->failure) { if (attempt->result || attempt->failure) {
utc_timestring(&attempt->end, timestr); utc_timestring(&attempt->end, timestr);
tal_append_fmt(ret, ", 'end_time': '%s'" json_out_addstr(ret, "end_time", timestr);
", 'duration_in_seconds': %"PRIu64, json_out_add_u64(ret, "duration_in_seconds",
timestr,
time_to_sec(time_between(attempt->end, time_to_sec(time_between(attempt->end,
attempt->start))); attempt->start)));
} }
if (tal_count(attempt->routehint)) { 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++) { for (size_t i = 0; i < tal_count(attempt->routehint); i++) {
tal_append_fmt(ret, "%s{" json_out_start(ret, NULL, '{');
" 'id': '%s'," json_out_addstr(ret, "id",
" 'channel': '%s',"
" 'fee_base_msat': %u,"
" 'fee_proportional_millionths': %u,"
" 'cltv_expiry_delta': %u }",
i == 0 ? "" : ", ",
type_to_string(tmpctx, struct node_id, type_to_string(tmpctx, struct node_id,
&attempt->routehint[i].pubkey), &attempt->routehint[i].pubkey));
json_out_addstr(ret, "channel",
type_to_string(tmpctx, type_to_string(tmpctx,
struct short_channel_id, struct short_channel_id,
&attempt->routehint[i].short_channel_id), &attempt->routehint[i].short_channel_id));
attempt->routehint[i].fee_base_msat, json_out_add_u64(ret, "fee_base_msat",
attempt->routehint[i].fee_proportional_millionths, 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); attempt->routehint[i].cltv_expiry_delta);
json_out_end(ret, '}');
} }
tal_append_fmt(ret, "]"); json_out_end(ret, ']');
} }
if (tal_count(attempt->excludes)) { if (tal_count(attempt->excludes)) {
for (size_t i = 0; i < tal_count(attempt->excludes); i++) { json_out_start(ret, "excluded_channels", '[');
if (i == 0) for (size_t i = 0; i < tal_count(attempt->excludes); i++)
tal_append_fmt(ret, ", 'excluded_channels': ["); json_out_addstr(ret, NULL, attempt->excludes[i]);
else json_out_end(ret, ']');
tal_append_fmt(ret, ", ");
tal_append_fmt(ret, "'%s'", attempt->excludes[i]);
}
tal_append_fmt(ret, "]");
} }
if (attempt->route) if (attempt->route)
tal_append_fmt(ret, ", 'route': %s", attempt->route); json_out_add_raw(ret, "route", attempt->route);
if (attempt->failure) if (attempt->failure)
tal_append_fmt(ret, ", 'failure': %s", attempt->failure); json_out_add_raw(ret, "failure", attempt->failure);
if (attempt->result) 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, 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; struct pay_status *ps;
const char *b11str; const char *b11str;
char *ret; struct json_out *ret;
bool some = false;
if (!param(cmd, buf, params, if (!param(cmd, buf, params,
p_opt("bolt11", param_string, &b11str), p_opt("bolt11", param_string, &b11str),
NULL)) NULL))
return 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! */ /* FIXME: Index by bolt11 string! */
list_for_each(&pay_status, ps, list) { list_for_each(&pay_status, ps, list) {
if (b11str && !streq(b11str, ps->bolt11)) if (b11str && !streq(b11str, ps->bolt11))
continue; continue;
if (some) json_out_start(ret, NULL, '{');
tal_append_fmt(&ret, ",\n"); json_out_addstr(ret, "bolt11", ps->bolt11);
some = true; json_out_add_u64(ret, "msatoshi",
ps->msat.millisatoshis); /* Raw: JSON */
tal_append_fmt(&ret, "{ 'bolt11': '%s'," json_out_addstr(ret, "amount_msat",
" 'msatoshi': %"PRIu64", "
" 'amount_msat': '%s', "
" 'destination': '%s'",
ps->bolt11,
ps->msat.millisatoshis, /* Raw: JSON */
type_to_string(tmpctx, struct 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) if (ps->label)
tal_append_fmt(&ret, ", 'label': '%s'", ps->label); json_out_addstr(ret, "label", ps->label);
if (ps->routehint_modifications) if (ps->routehint_modifications)
tal_append_fmt(&ret, ", 'routehint_modifications': '%s'", json_out_addstr(ret, "routehint_modifications",
ps->routehint_modifications); ps->routehint_modifications);
if (ps->shadow && !streq(ps->shadow, "")) if (ps->shadow && !streq(ps->shadow, ""))
tal_append_fmt(&ret, ", 'shadow': '%s'", ps->shadow); json_out_addstr(ret, "shadow", ps->shadow);
if (ps->exclusions) if (ps->exclusions)
tal_append_fmt(&ret, ", 'local_exclusions': '%s'", json_out_addstr(ret, "local_exclusions", ps->exclusions);
ps->exclusions);
assert(tal_count(ps->attempts)); assert(tal_count(ps->attempts));
for (size_t i = 0; i < tal_count(ps->attempts); i++) { json_out_start(ret, "attempts", '[');
if (i == 0) for (size_t i = 0; i < tal_count(ps->attempts); i++)
tal_append_fmt(&ret, ", 'attempts': ["); add_attempt(ret, ps, &ps->attempts[i]);
else json_out_end(ret, ']');
tal_append_fmt(&ret, ","); json_out_end(ret, '}');
add_attempt(&ret, ps, &ps->attempts[i]);
}
tal_append_fmt(&ret, "] }");
} }
tal_append_fmt(&ret, "] }"); json_out_end(ret, ']');
json_out_end(ret, '}');
return command_success(cmd, ret); return command_success(cmd, ret);
} }
static const jsmntok_t *copy_member(char **ret, /* Copy field and member to output, if it exists: return member */
const char *buf, static const jsmntok_t *copy_member(struct json_out *ret,
const jsmntok_t *result, const char *buf, const jsmntok_t *obj,
const char *membername, const char *membername)
const char *term)
{ {
const jsmntok_t *m = json_get_member(buf, result, membername); const jsmntok_t *m = json_get_member(buf, obj, membername);
if (m) if (!m)
tal_append_fmt(ret, "'%s': %.*s%s", return NULL;
membername,
json_tok_full_len(m), json_tok_full(buf, m), /* Literal copy: it's already JSON escaped, and may be a string. */
term); json_out_add_raw_len(ret, membername,
json_tok_full(buf, m), json_tok_full_len(m));
return m; return m;
} }
@ -1175,33 +1186,30 @@ static struct command_result *listsendpays_done(struct command *cmd,
{ {
size_t i; size_t i;
const jsmntok_t *t, *arr; const jsmntok_t *t, *arr;
char *ret; struct json_out *ret;
bool some = false;
arr = json_get_member(buf, result, "payments"); arr = json_get_member(buf, result, "payments");
if (!arr || arr->type != JSMN_ARRAY) if (!arr || arr->type != JSMN_ARRAY)
return command_fail(cmd, LIGHTNINGD, return command_fail(cmd, LIGHTNINGD,
"Unexpected non-array result from listsendpays"); "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) { json_for_each_arr(i, t, arr) {
const jsmntok_t *status, *b11; const jsmntok_t *status, *b11;
if (some) json_out_start(ret, NULL, '{');
tal_append_fmt(&ret, ",\n");
some = true;
tal_append_fmt(&ret, "{");
/* Old payments didn't have bolt11 field */ /* Old payments didn't have bolt11 field */
b11 = copy_member(&ret, buf, t, "bolt11", ","); b11 = copy_member(ret, buf, t, "bolt11");
if (!b11) { if (!b11) {
if (b11str) { if (b11str) {
/* If it's a single query, we can fake it */ /* If it's a single query, we can fake it */
tal_append_fmt(&ret, "'bolt11': '%s',", b11str); json_out_addstr(ret, "bolt11", b11str);
} else { } else {
copy_member(&ret, buf, t, "payment_hash", ","); copy_member(ret, buf, t, "payment_hash");
copy_member(&ret, buf, t, "destination", ","); copy_member(ret, buf, t, "destination");
copy_member(&ret, buf, t, "amount_msat", ","); copy_member(ret, buf, t, "amount_msat");
} }
} }
@ -1210,19 +1218,20 @@ static struct command_result *listsendpays_done(struct command *cmd,
if (status) { if (status) {
if (json_tok_streq(buf, status, "failed") if (json_tok_streq(buf, status, "failed")
&& attempt_ongoing(buf, b11)) { && attempt_ongoing(buf, b11)) {
tal_append_fmt(&ret, "'status': 'pending',"); json_out_addstr(ret, "status", "pending");
} else { } else {
copy_member(&ret, buf, t, "status", ","); copy_member(ret, buf, t, "status");
if (json_tok_streq(buf, status, "complete")) if (json_tok_streq(buf, status, "complete"))
copy_member(&ret, buf, t, copy_member(ret, buf, t,
"payment_preimage", ","); "payment_preimage");
} }
} }
copy_member(&ret, buf, t, "label", ","); copy_member(ret, buf, t, "label");
copy_member(&ret, buf, t, "amount_sent_msat", ""); copy_member(ret, buf, t, "amount_sent_msat");
tal_append_fmt(&ret, "}"); json_out_end(ret, '}');
} }
tal_append_fmt(&ret, "] }"); json_out_end(ret, ']');
json_out_end(ret, '}');
return command_success(cmd, ret); return command_success(cmd, ret);
} }
@ -1230,7 +1239,7 @@ static struct command_result *json_listpays(struct command *cmd,
const char *buf, const char *buf,
const jsmntok_t *params) 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 */ /* FIXME: would be nice to parse as a bolt11 so check worked in future */
if (!param(cmd, buf, params, if (!param(cmd, buf, params,
@ -1238,26 +1247,25 @@ static struct command_result *json_listpays(struct command *cmd,
NULL)) NULL))
return NULL; return NULL;
if (b11str)
paramstr = tal_fmt(tmpctx, "'bolt11' : '%s'", b11str);
else
paramstr = "";
return send_outreq(cmd, "listsendpays", return send_outreq(cmd, "listsendpays",
listsendpays_done, forward_error, listsendpays_done, forward_error,
cast_const(char *, b11str), 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) static void init(struct plugin_conn *rpc)
{ {
const char *field; 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)) if (!node_id_from_hexstr(field, strlen(field), &my_id))
plugin_err("getinfo didn't contain valid id: '%s'", field); plugin_err("getinfo didn't contain valid id: '%s'", field);
field = rpc_delve(tmpctx, "listconfigs", field = rpc_delve(tmpctx, "listconfigs",
"'config': 'max-locktime-blocks'", take(json_out_obj(NULL,
"config", "max-locktime-blocks")),
rpc, ".max-locktime-blocks"); rpc, ".max-locktime-blocks");
maxdelay_default = atoi(field); maxdelay_default = atoi(field);
} }

Loading…
Cancel
Save