2019-01-15 05:14:27 +01:00
|
|
|
#include <ccan/array_size/array_size.h>
|
|
|
|
#include <ccan/intmap/intmap.h>
|
|
|
|
#include <ccan/tal/str/str.h>
|
2019-01-15 05:16:27 +01:00
|
|
|
#include <ccan/time/time.h>
|
2019-02-21 03:38:35 +01:00
|
|
|
#include <common/amount.h>
|
2019-01-15 05:14:27 +01:00
|
|
|
#include <common/bolt11.h>
|
2019-01-15 11:04:07 +01:00
|
|
|
#include <common/pseudorand.h>
|
2019-01-15 05:14:27 +01:00
|
|
|
#include <common/type_to_string.h>
|
2019-01-15 11:04:01 +01:00
|
|
|
#include <gossipd/gossip_constants.h>
|
2019-01-15 05:14:27 +01:00
|
|
|
#include <plugins/libplugin.h>
|
2019-01-15 11:04:07 +01:00
|
|
|
#include <stdio.h>
|
2019-01-15 05:14:27 +01:00
|
|
|
|
2019-01-15 05:20:27 +01:00
|
|
|
/* Public key of this node. */
|
|
|
|
static struct pubkey my_id;
|
2019-01-15 05:21:27 +01:00
|
|
|
static unsigned int maxdelay_default;
|
2019-01-15 11:04:07 +01:00
|
|
|
static LIST_HEAD(pay_status);
|
2019-01-15 05:20:27 +01:00
|
|
|
|
2019-01-15 05:16:27 +01:00
|
|
|
struct pay_attempt {
|
2019-01-15 11:04:08 +01:00
|
|
|
/* What we changed when starting this attempt. */
|
|
|
|
const char *why;
|
2019-01-15 11:04:07 +01:00
|
|
|
/* Time we started & finished attempt */
|
|
|
|
struct timeabs start, end;
|
|
|
|
/* Route hint we were using (if any) */
|
|
|
|
struct route_info *routehint;
|
|
|
|
/* Channels we excluded when doing route lookup. */
|
|
|
|
const char **excludes;
|
|
|
|
/* Route we got (NULL == route lookup fail). */
|
2019-01-15 05:16:27 +01:00
|
|
|
const char *route;
|
2019-01-15 11:04:07 +01:00
|
|
|
/* The failure result (NULL on success) */
|
2019-01-15 05:16:27 +01:00
|
|
|
const char *failure;
|
2019-01-15 11:04:07 +01:00
|
|
|
/* The non-failure result (NULL on failure) */
|
|
|
|
const char *result;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct pay_status {
|
|
|
|
/* Destination, as text */
|
|
|
|
const char *dest;
|
|
|
|
|
|
|
|
/* We're in 'pay_status' global list. */
|
|
|
|
struct list_node list;
|
|
|
|
|
|
|
|
/* Description user provided (if any) */
|
|
|
|
const char *desc;
|
|
|
|
/* Amount they wanted to pay. */
|
2019-02-21 04:45:44 +01:00
|
|
|
struct amount_msat msat;
|
2019-01-15 11:04:07 +01:00
|
|
|
/* CLTV delay required by destination. */
|
|
|
|
u32 final_cltv;
|
|
|
|
/* Bolt11 invoice. */
|
|
|
|
const char *bolt11;
|
|
|
|
|
|
|
|
/* What we did about routehints (if anything) */
|
|
|
|
const char *routehint_modifications;
|
|
|
|
|
|
|
|
/* Details of shadow route we chose (if any) */
|
|
|
|
char *shadow;
|
|
|
|
|
|
|
|
/* Details of initial exclusions (if any) */
|
|
|
|
const char *exclusions;
|
|
|
|
|
|
|
|
/* Array of payment attempts. */
|
|
|
|
struct pay_attempt *attempts;
|
2019-01-15 05:16:27 +01:00
|
|
|
};
|
|
|
|
|
2019-01-15 05:14:27 +01:00
|
|
|
struct pay_command {
|
2019-01-15 05:16:27 +01:00
|
|
|
/* Destination, as text */
|
|
|
|
const char *dest;
|
|
|
|
|
|
|
|
/* How much we're paying, and what riskfactor for routing. */
|
2019-02-21 04:45:44 +01:00
|
|
|
struct amount_msat msat;
|
2019-01-15 05:16:27 +01:00
|
|
|
double riskfactor;
|
2019-01-15 05:22:27 +01:00
|
|
|
unsigned int final_cltv;
|
2019-01-15 05:16:27 +01:00
|
|
|
|
2019-01-15 05:19:27 +01:00
|
|
|
/* Limits on what routes we'll accept. */
|
|
|
|
double maxfeepercent;
|
|
|
|
unsigned int maxdelay;
|
2019-02-21 04:45:44 +01:00
|
|
|
struct amount_msat exemptfee;
|
2019-01-15 05:19:27 +01:00
|
|
|
|
2019-01-15 05:14:27 +01:00
|
|
|
/* Payment hash, as text. */
|
|
|
|
const char *payment_hash;
|
|
|
|
|
|
|
|
/* Description, if any. */
|
|
|
|
const char *desc;
|
2019-01-15 05:16:27 +01:00
|
|
|
|
|
|
|
/* Chatty description of attempts. */
|
2019-01-15 11:04:07 +01:00
|
|
|
struct pay_status *ps;
|
2019-01-15 05:16:27 +01:00
|
|
|
|
2019-01-15 11:04:08 +01:00
|
|
|
/* Error to use if getroute says it can't find route. */
|
|
|
|
const char *expensive_route;
|
|
|
|
|
2019-01-15 05:16:27 +01:00
|
|
|
/* Time to stop retrying. */
|
|
|
|
struct timeabs stoptime;
|
|
|
|
|
|
|
|
/* Channels which have failed us. */
|
|
|
|
const char **excludes;
|
2019-01-15 11:04:01 +01:00
|
|
|
|
2019-02-01 04:03:27 +01:00
|
|
|
/* Current routehint, if any. */
|
|
|
|
struct route_info *current_routehint;
|
|
|
|
|
|
|
|
/* Any remaining routehints to try. */
|
2019-01-15 11:04:01 +01:00
|
|
|
struct route_info **routehints;
|
2019-01-15 11:04:07 +01:00
|
|
|
|
|
|
|
/* Current node during shadow route calculation. */
|
2019-01-16 21:37:11 +01:00
|
|
|
const char *shadow_dest;
|
2019-01-15 05:14:27 +01:00
|
|
|
};
|
|
|
|
|
2019-01-15 11:04:07 +01:00
|
|
|
static struct pay_attempt *current_attempt(struct pay_command *pc)
|
|
|
|
{
|
|
|
|
return &pc->ps->attempts[tal_count(pc->ps->attempts)-1];
|
|
|
|
}
|
|
|
|
|
|
|
|
PRINTF_FMT(2,3) static void attempt_failed_fmt(struct pay_command *pc, const char *fmt, ...)
|
|
|
|
{
|
|
|
|
struct pay_attempt *attempt = current_attempt(pc);
|
|
|
|
va_list ap;
|
|
|
|
|
|
|
|
va_start(ap,fmt);
|
|
|
|
attempt->failure = tal_vfmt(pc->ps->attempts, fmt, ap);
|
|
|
|
attempt->end = time_now();
|
|
|
|
va_end(ap);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void attempt_failed_tok(struct pay_command *pc, const char *method,
|
2019-01-15 11:04:07 +01:00
|
|
|
const char *buf, const jsmntok_t *errtok)
|
2019-01-15 11:04:07 +01:00
|
|
|
{
|
2019-01-15 11:04:07 +01:00
|
|
|
const jsmntok_t *msg = json_get_member(buf, errtok, "message");
|
|
|
|
|
|
|
|
if (msg)
|
|
|
|
attempt_failed_fmt(pc, "%.*sCall to %s:%.*s",
|
|
|
|
msg->start - errtok->start,
|
|
|
|
buf + errtok->start,
|
|
|
|
method,
|
|
|
|
errtok->end - msg->start,
|
|
|
|
buf + msg->start);
|
|
|
|
else
|
|
|
|
attempt_failed_fmt(pc,
|
|
|
|
"{ 'message': 'Call to %s failed', %.*s",
|
|
|
|
method,
|
|
|
|
errtok->end - errtok->start - 1,
|
|
|
|
buf + errtok->start + 1);
|
2019-01-15 11:04:07 +01:00
|
|
|
}
|
|
|
|
|
2019-01-15 05:16:27 +01:00
|
|
|
static struct command_result *start_pay_attempt(struct command *cmd,
|
2019-01-15 11:04:08 +01:00
|
|
|
struct pay_command *pc,
|
|
|
|
const char *fmt, ...);
|
2019-01-15 05:16:27 +01:00
|
|
|
|
2019-01-15 11:04:07 +01:00
|
|
|
/* Is this (erring) channel within the routehint itself? */
|
|
|
|
static bool channel_in_routehint(const struct route_info *routehint,
|
|
|
|
const char *buf, const jsmntok_t *scidtok)
|
|
|
|
{
|
|
|
|
struct short_channel_id scid;
|
|
|
|
|
2019-02-08 00:23:25 +01:00
|
|
|
if (!json_to_short_channel_id(buf, scidtok, &scid, false))
|
2019-01-15 11:04:07 +01:00
|
|
|
plugin_err("bad erring_channel '%.*s'",
|
|
|
|
scidtok->end - scidtok->start, buf + scidtok->start);
|
|
|
|
|
|
|
|
for (size_t i = 0; i < tal_count(routehint); i++)
|
|
|
|
if (short_channel_id_eq(&scid, &routehint[i].short_channel_id))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-01-15 05:16:27 +01:00
|
|
|
static struct command_result *waitsendpay_expired(struct command *cmd,
|
|
|
|
struct pay_command *pc)
|
|
|
|
{
|
|
|
|
char *errmsg, *data;
|
|
|
|
|
|
|
|
errmsg = tal_fmt(pc, "Gave up after %zu attempts",
|
2019-01-15 11:04:07 +01:00
|
|
|
tal_count(pc->ps->attempts));
|
2019-01-15 05:16:27 +01:00
|
|
|
data = tal_strdup(pc, "'attempts': [ ");
|
2019-01-15 11:04:07 +01:00
|
|
|
for (size_t i = 0; i < tal_count(pc->ps->attempts); i++) {
|
|
|
|
if (pc->ps->attempts[i].route)
|
2019-01-15 11:04:07 +01:00
|
|
|
tal_append_fmt(&data, "%s { 'route': %s,\n 'failure': %s\n }",
|
2019-01-15 11:04:07 +01:00
|
|
|
i == 0 ? "" : ",",
|
|
|
|
pc->ps->attempts[i].route,
|
|
|
|
pc->ps->attempts[i].failure);
|
|
|
|
else
|
2019-01-15 11:04:07 +01:00
|
|
|
tal_append_fmt(&data, "%s { 'failure': %s\n }",
|
2019-01-15 11:04:07 +01:00
|
|
|
i == 0 ? "" : ",",
|
|
|
|
pc->ps->attempts[i].failure);
|
|
|
|
}
|
2019-01-15 05:16:27 +01:00
|
|
|
tal_append_fmt(&data, "]");
|
|
|
|
return command_done_err(cmd, PAY_STOPPED_RETRYING, errmsg, data);
|
|
|
|
}
|
|
|
|
|
2019-01-15 11:04:08 +01:00
|
|
|
static struct command_result *next_routehint(struct command *cmd,
|
|
|
|
struct pay_command *pc)
|
|
|
|
{
|
2019-02-01 04:03:27 +01:00
|
|
|
if (tal_count(pc->routehints) > 0) {
|
|
|
|
pc->current_routehint = pc->routehints[0];
|
|
|
|
tal_arr_remove(&pc->routehints, 0);
|
|
|
|
return start_pay_attempt(cmd, pc, "Trying route hint");
|
|
|
|
}
|
|
|
|
|
|
|
|
/* No (more) routehints; we're out of routes. */
|
|
|
|
/* If we eliminated one because it was too pricy, return that. */
|
|
|
|
if (pc->expensive_route)
|
|
|
|
return command_fail(cmd, PAY_ROUTE_TOO_EXPENSIVE,
|
|
|
|
"%s", pc->expensive_route);
|
|
|
|
|
|
|
|
return command_fail(cmd, PAY_ROUTE_NOT_FOUND,
|
|
|
|
"Could not find a route");
|
2019-01-15 11:04:08 +01:00
|
|
|
}
|
|
|
|
|
2019-01-15 05:16:27 +01:00
|
|
|
static struct command_result *waitsendpay_error(struct command *cmd,
|
|
|
|
const char *buf,
|
|
|
|
const jsmntok_t *error,
|
|
|
|
struct pay_command *pc)
|
|
|
|
{
|
|
|
|
const jsmntok_t *codetok, *scidtok, *dirtok;
|
|
|
|
int code;
|
|
|
|
|
2019-01-15 11:04:07 +01:00
|
|
|
attempt_failed_tok(pc, "waitsendpay", buf, error);
|
|
|
|
|
2019-01-15 05:16:27 +01:00
|
|
|
codetok = json_get_member(buf, error, "code");
|
|
|
|
if (!json_to_int(buf, codetok, &code))
|
|
|
|
plugin_err("waitsendpay error gave no 'code'? '%.*s'",
|
|
|
|
error->end - error->start, buf + error->start);
|
|
|
|
|
|
|
|
/* FIXME: Handle PAY_UNPARSEABLE_ONION! */
|
|
|
|
|
|
|
|
/* Many error codes are final. */
|
|
|
|
if (code != PAY_TRY_OTHER_ROUTE) {
|
|
|
|
return forward_error(cmd, buf, error, pc);
|
|
|
|
}
|
|
|
|
|
|
|
|
scidtok = json_delve(buf, error, ".data.erring_channel");
|
|
|
|
if (!scidtok)
|
|
|
|
plugin_err("waitsendpay error no erring_channel '%.*s'",
|
|
|
|
error->end - error->start, buf + error->start);
|
|
|
|
dirtok = json_delve(buf, error, ".data.erring_direction");
|
|
|
|
if (!dirtok)
|
|
|
|
plugin_err("waitsendpay error no erring_direction '%.*s'",
|
|
|
|
error->end - error->start, buf + error->start);
|
|
|
|
|
|
|
|
if (time_after(time_now(), pc->stoptime)) {
|
|
|
|
return waitsendpay_expired(cmd, pc);
|
|
|
|
}
|
|
|
|
|
2019-02-01 04:03:27 +01:00
|
|
|
/* If failure is in routehint part, try next one */
|
|
|
|
if (channel_in_routehint(pc->current_routehint, buf, scidtok))
|
2019-01-15 11:04:08 +01:00
|
|
|
return next_routehint(cmd, pc);
|
|
|
|
|
|
|
|
/* Otherwise, add erring channel to exclusion list. */
|
|
|
|
tal_arr_expand(&pc->excludes,
|
|
|
|
tal_fmt(pc->excludes, "%.*s/%c",
|
|
|
|
scidtok->end - scidtok->start,
|
|
|
|
buf + scidtok->start,
|
|
|
|
buf[dirtok->start]));
|
2019-01-15 05:16:27 +01:00
|
|
|
/* Try again. */
|
2019-01-15 11:04:08 +01:00
|
|
|
return start_pay_attempt(cmd, pc, "Excluded channel %s",
|
|
|
|
pc->excludes[tal_count(pc->excludes)-1]);
|
2019-01-15 05:16:27 +01:00
|
|
|
}
|
|
|
|
|
2019-01-15 11:04:07 +01:00
|
|
|
static struct command_result *waitsendpay_done(struct command *cmd,
|
|
|
|
const char *buf,
|
|
|
|
const jsmntok_t *result,
|
|
|
|
struct pay_command *pc)
|
|
|
|
{
|
|
|
|
struct pay_attempt *attempt = current_attempt(pc);
|
|
|
|
|
|
|
|
attempt->result = json_strdup(pc->ps->attempts, buf, result);
|
|
|
|
attempt->end = time_now();
|
|
|
|
|
|
|
|
return forward_result(cmd, buf, result, pc);
|
|
|
|
}
|
|
|
|
|
2019-01-15 05:14:27 +01:00
|
|
|
static struct command_result *sendpay_done(struct command *cmd,
|
|
|
|
const char *buf,
|
|
|
|
const jsmntok_t *result,
|
|
|
|
struct pay_command *pc)
|
|
|
|
{
|
|
|
|
return send_outreq(cmd, "waitsendpay",
|
2019-01-15 11:04:07 +01:00
|
|
|
waitsendpay_done, waitsendpay_error, pc,
|
2019-01-15 05:14:27 +01:00
|
|
|
"'payment_hash': '%s', 'timeout': 60",
|
|
|
|
pc->payment_hash);
|
|
|
|
}
|
|
|
|
|
2019-01-15 11:04:01 +01:00
|
|
|
/* Calculate how many millisatoshi we need at the start of this route
|
|
|
|
* to get msatoshi to the end. */
|
2019-02-21 04:45:44 +01:00
|
|
|
static bool route_msatoshi(struct amount_msat *total,
|
|
|
|
const struct amount_msat msat,
|
|
|
|
const struct route_info *route, size_t num_route)
|
2019-01-15 11:04:01 +01:00
|
|
|
{
|
2019-02-21 04:45:44 +01:00
|
|
|
*total = msat;
|
2019-01-15 11:04:01 +01:00
|
|
|
for (ssize_t i = num_route - 1; i >= 0; i--) {
|
2019-02-21 04:45:44 +01:00
|
|
|
if (!amount_msat_add_fee(total,
|
|
|
|
route[i].fee_base_msat,
|
|
|
|
route[i].fee_proportional_millionths))
|
|
|
|
return false;
|
2019-01-15 11:04:01 +01:00
|
|
|
}
|
2019-02-21 04:45:44 +01:00
|
|
|
return true;
|
2019-01-15 11:04:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Calculate cltv we need at the start of this route to get cltv at the end. */
|
|
|
|
static u32 route_cltv(u32 cltv,
|
|
|
|
const struct route_info *route, size_t num_route)
|
|
|
|
{
|
|
|
|
for (size_t i = 0; i < num_route; i++)
|
|
|
|
cltv += route[i].cltv_expiry_delta;
|
|
|
|
return cltv;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* The pubkey to use is the destination of this routehint. */
|
|
|
|
static const char *route_pubkey(const tal_t *ctx,
|
|
|
|
const struct pay_command *pc,
|
|
|
|
const struct route_info *routehint,
|
|
|
|
size_t n)
|
|
|
|
{
|
|
|
|
if (n == tal_count(routehint))
|
|
|
|
return pc->dest;
|
|
|
|
return type_to_string(ctx, struct pubkey, &routehint[n].pubkey);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const char *join_routehint(const tal_t *ctx,
|
|
|
|
const char *buf,
|
|
|
|
const jsmntok_t *route,
|
|
|
|
const struct pay_command *pc,
|
|
|
|
const struct route_info *routehint)
|
|
|
|
{
|
|
|
|
char *ret;
|
|
|
|
|
|
|
|
/* Truncate closing ] from route */
|
|
|
|
ret = tal_strndup(ctx, buf + route->start, route->end - route->start - 1);
|
|
|
|
for (size_t i = 0; i < tal_count(routehint); i++) {
|
2019-02-21 04:45:44 +01:00
|
|
|
/* amount to be received by *destination* */
|
|
|
|
struct amount_msat dest_amount;
|
|
|
|
|
|
|
|
if (!route_msatoshi(&dest_amount, pc->msat,
|
|
|
|
routehint + i + 1,
|
|
|
|
tal_count(routehint) - i - 1))
|
|
|
|
return tal_free(ret);
|
|
|
|
|
2019-01-15 11:04:01 +01:00
|
|
|
tal_append_fmt(&ret, ", {"
|
|
|
|
" 'id': '%s',"
|
|
|
|
" 'channel': '%s',"
|
2019-02-21 04:45:44 +01:00
|
|
|
" 'msatoshi': '%s',"
|
2019-01-15 11:04:01 +01:00
|
|
|
" 'delay': %u }",
|
|
|
|
/* pubkey of *destination* */
|
|
|
|
route_pubkey(tmpctx, pc, routehint, i + 1),
|
|
|
|
type_to_string(tmpctx, struct short_channel_id,
|
|
|
|
&routehint[i].short_channel_id),
|
2019-02-21 04:45:44 +01:00
|
|
|
type_to_string(tmpctx, struct amount_msat,
|
|
|
|
&dest_amount),
|
2019-01-15 11:04:01 +01:00
|
|
|
/* cltv for *destination* */
|
|
|
|
route_cltv(pc->final_cltv, routehint + i + 1,
|
|
|
|
tal_count(routehint) - i - 1));
|
|
|
|
}
|
|
|
|
/* Put ] back */
|
|
|
|
tal_append_fmt(&ret, "]");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2019-01-15 11:04:07 +01:00
|
|
|
static struct command_result *sendpay_error(struct command *cmd,
|
|
|
|
const char *buf,
|
|
|
|
const jsmntok_t *error,
|
|
|
|
struct pay_command *pc)
|
|
|
|
{
|
|
|
|
attempt_failed_tok(pc, "sendpay", buf, error);
|
|
|
|
|
|
|
|
return forward_error(cmd, buf, error, pc);
|
|
|
|
}
|
|
|
|
|
2019-01-15 11:04:08 +01:00
|
|
|
static const jsmntok_t *find_worst_channel(const char *buf,
|
|
|
|
const jsmntok_t *route,
|
|
|
|
const char *fieldname,
|
|
|
|
u64 final)
|
|
|
|
{
|
|
|
|
u64 prev = final, worstval = 0;
|
2019-01-17 03:12:13 +01:00
|
|
|
const jsmntok_t *worst = NULL, *t;
|
|
|
|
size_t i;
|
2019-01-15 11:04:08 +01:00
|
|
|
|
2019-01-17 03:12:13 +01:00
|
|
|
json_for_each_arr(i, t, route) {
|
2019-01-15 11:04:08 +01:00
|
|
|
u64 val;
|
|
|
|
|
2019-01-17 03:12:13 +01:00
|
|
|
json_to_u64(buf, json_get_member(buf, t, fieldname), &val);
|
2019-01-15 11:04:08 +01:00
|
|
|
if (worst == NULL || val - prev > worstval) {
|
2019-01-17 03:12:13 +01:00
|
|
|
worst = t;
|
2019-01-15 11:04:08 +01:00
|
|
|
worstval = val - prev;
|
|
|
|
}
|
|
|
|
prev = val;
|
|
|
|
}
|
|
|
|
|
|
|
|
return worst;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Can't exclude if it's in routehint itself. */
|
|
|
|
static bool maybe_exclude(struct pay_command *pc,
|
|
|
|
const char *buf, const jsmntok_t *route)
|
|
|
|
{
|
|
|
|
const jsmntok_t *scid, *dir;
|
|
|
|
|
|
|
|
scid = json_get_member(buf, route, "channel");
|
|
|
|
|
2019-02-01 04:03:27 +01:00
|
|
|
if (channel_in_routehint(pc->current_routehint, buf, scid))
|
2019-01-15 11:04:08 +01:00
|
|
|
return false;
|
|
|
|
|
|
|
|
dir = json_get_member(buf, route, "direction");
|
|
|
|
tal_arr_expand(&pc->excludes,
|
|
|
|
tal_fmt(pc->excludes, "%.*s/%c",
|
|
|
|
scid->end - scid->start,
|
|
|
|
buf + scid->start,
|
|
|
|
buf[dir->start]));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-01-15 05:14:27 +01:00
|
|
|
static struct command_result *getroute_done(struct command *cmd,
|
|
|
|
const char *buf,
|
|
|
|
const jsmntok_t *result,
|
|
|
|
struct pay_command *pc)
|
|
|
|
{
|
2019-01-15 11:04:07 +01:00
|
|
|
struct pay_attempt *attempt = current_attempt(pc);
|
2019-01-15 05:14:27 +01:00
|
|
|
const jsmntok_t *t = json_get_member(buf, result, "route");
|
|
|
|
char *json_desc;
|
2019-02-21 03:39:21 +01:00
|
|
|
struct amount_msat fee;
|
2019-01-15 05:19:27 +01:00
|
|
|
u32 delay;
|
|
|
|
double feepercent;
|
|
|
|
|
2019-01-15 05:14:27 +01:00
|
|
|
if (!t)
|
|
|
|
plugin_err("getroute gave no 'route'? '%.*s'",
|
|
|
|
result->end - result->start, buf);
|
|
|
|
|
2019-02-21 04:45:44 +01:00
|
|
|
if (pc->current_routehint) {
|
2019-01-15 11:04:07 +01:00
|
|
|
attempt->route = join_routehint(pc->ps->attempts, buf, t,
|
2019-02-01 04:03:27 +01:00
|
|
|
pc, pc->current_routehint);
|
2019-02-21 04:45:44 +01:00
|
|
|
if (!attempt->route) {
|
|
|
|
attempt_failed_fmt(pc,
|
|
|
|
"{ 'message': 'Joining routehint gave absurd fee' }");
|
|
|
|
return next_routehint(cmd, pc);
|
|
|
|
}
|
|
|
|
} else
|
2019-01-15 11:04:07 +01:00
|
|
|
attempt->route = json_strdup(pc->ps->attempts, buf, t);
|
|
|
|
|
2019-02-21 03:39:21 +01:00
|
|
|
if (!json_to_msat(buf, json_delve(buf, t, "[0].msatoshi"), &fee))
|
|
|
|
plugin_err("getroute with invalid msatoshi? %.*s",
|
2019-01-15 05:19:27 +01:00
|
|
|
result->end - result->start, buf);
|
2019-02-21 04:45:44 +01:00
|
|
|
if (!amount_msat_sub(&fee, fee, pc->msat))
|
|
|
|
plugin_err("final amount %s less than paid %s",
|
|
|
|
type_to_string(tmpctx, struct amount_msat, &fee),
|
|
|
|
type_to_string(tmpctx, struct amount_msat, &pc->msat));
|
2019-01-15 05:19:27 +01:00
|
|
|
|
|
|
|
if (!json_to_number(buf, json_delve(buf, t, "[0].delay"), &delay))
|
2019-02-21 03:39:21 +01:00
|
|
|
plugin_err("getroute with invalid delay? %.*s",
|
2019-01-15 05:19:27 +01:00
|
|
|
result->end - result->start, buf);
|
|
|
|
|
|
|
|
/* Casting u64 to double will lose some precision. The loss of precision
|
|
|
|
* in feepercent will be like 3.0000..(some dots)..1 % - 3.0 %.
|
|
|
|
* That loss will not be representable in double. So, it's Okay to
|
|
|
|
* cast u64 to double for feepercent calculation. */
|
2019-02-21 04:45:56 +01:00
|
|
|
feepercent = ((double)fee.millisatoshis) * 100.0 / ((double) pc->msat.millisatoshis); /* Raw: fee double manipulation */
|
2019-01-15 05:19:27 +01:00
|
|
|
|
2019-02-21 04:45:44 +01:00
|
|
|
if (amount_msat_greater(fee, pc->exemptfee)
|
|
|
|
&& feepercent > pc->maxfeepercent) {
|
2019-01-15 11:04:08 +01:00
|
|
|
const jsmntok_t *charger;
|
|
|
|
|
2019-02-21 03:39:21 +01:00
|
|
|
attempt_failed_fmt(pc, "{ 'message': 'Route wanted fee of %s' }",
|
|
|
|
type_to_string(tmpctx, struct amount_msat,
|
|
|
|
&fee));
|
2019-01-15 11:04:07 +01:00
|
|
|
|
2019-01-15 11:04:08 +01:00
|
|
|
/* Remember this if we eliminating this causes us to have no
|
|
|
|
* routes at all! */
|
|
|
|
if (!pc->expensive_route)
|
|
|
|
pc->expensive_route
|
2019-02-21 03:39:21 +01:00
|
|
|
= tal_fmt(pc, "Route wanted fee of %s",
|
|
|
|
type_to_string(tmpctx,
|
|
|
|
struct amount_msat,
|
|
|
|
&fee));
|
2019-01-15 11:04:08 +01:00
|
|
|
|
|
|
|
/* Try excluding most fee-charging channel (unless it's in
|
|
|
|
* routeboost). */
|
2019-02-21 04:45:56 +01:00
|
|
|
charger = find_worst_channel(buf, t, "msatoshi", pc->msat.millisatoshis); /* Raw: shared function needs u64 */
|
2019-01-15 11:04:08 +01:00
|
|
|
if (maybe_exclude(pc, buf, charger)) {
|
|
|
|
return start_pay_attempt(cmd, pc,
|
|
|
|
"Excluded expensive channel %s",
|
|
|
|
pc->excludes[tal_count(pc->excludes)-1]);
|
|
|
|
}
|
2019-01-15 11:04:08 +01:00
|
|
|
|
2019-02-01 04:03:27 +01:00
|
|
|
return next_routehint(cmd, pc);
|
2019-01-15 05:19:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (delay > pc->maxdelay) {
|
2019-01-15 11:04:08 +01:00
|
|
|
const jsmntok_t *delayer;
|
|
|
|
|
|
|
|
attempt_failed_fmt(pc,
|
|
|
|
"{ 'message': 'Route wanted delay of %u blocks' }",
|
|
|
|
delay);
|
|
|
|
|
|
|
|
/* Remember this if we eliminating this causes us to have no
|
|
|
|
* routes at all! */
|
|
|
|
if (!pc->expensive_route)
|
|
|
|
pc->expensive_route
|
|
|
|
= tal_fmt(pc, "Route wanted delay of %u blocks",
|
|
|
|
delay);
|
|
|
|
|
|
|
|
delayer = find_worst_channel(buf, t, "delay", pc->final_cltv);
|
|
|
|
|
|
|
|
/* Try excluding most delaying channel (unless it's in
|
|
|
|
* routeboost). */
|
2019-01-15 11:04:08 +01:00
|
|
|
if (maybe_exclude(pc, buf, delayer)) {
|
|
|
|
return start_pay_attempt(cmd, pc,
|
|
|
|
"Excluded delaying channel %s",
|
|
|
|
pc->excludes[tal_count(pc->excludes)-1]);
|
|
|
|
}
|
2019-01-15 11:04:07 +01:00
|
|
|
|
2019-02-01 04:03:27 +01:00
|
|
|
return next_routehint(cmd, pc);
|
2019-01-15 05:19:27 +01:00
|
|
|
}
|
|
|
|
|
2019-01-15 05:14:27 +01:00
|
|
|
if (pc->desc)
|
|
|
|
json_desc = tal_fmt(pc, ", 'description': '%s'", pc->desc);
|
|
|
|
else
|
|
|
|
json_desc = "";
|
|
|
|
|
2019-01-15 11:04:07 +01:00
|
|
|
return send_outreq(cmd, "sendpay", sendpay_done, sendpay_error, pc,
|
2019-02-23 04:11:14 +01:00
|
|
|
"'route': %s, 'payment_hash': '%s', 'bolt11': '%s'%s",
|
2019-01-15 11:04:07 +01:00
|
|
|
attempt->route,
|
2019-01-15 05:14:27 +01:00
|
|
|
pc->payment_hash,
|
2019-02-23 04:11:14 +01:00
|
|
|
pc->ps->bolt11,
|
2019-01-15 05:14:27 +01:00
|
|
|
json_desc);
|
2019-01-15 11:04:07 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct command_result *getroute_error(struct command *cmd,
|
|
|
|
const char *buf,
|
2019-01-15 11:04:07 +01:00
|
|
|
const jsmntok_t *error,
|
2019-01-15 11:04:07 +01:00
|
|
|
struct pay_command *pc)
|
|
|
|
{
|
2019-02-01 04:03:27 +01:00
|
|
|
int code;
|
|
|
|
const jsmntok_t *codetok;
|
|
|
|
|
2019-01-15 11:04:07 +01:00
|
|
|
attempt_failed_tok(pc, "getroute", buf, error);
|
|
|
|
|
2019-02-01 04:03:27 +01:00
|
|
|
codetok = json_get_member(buf, error, "code");
|
|
|
|
if (!json_to_int(buf, codetok, &code))
|
|
|
|
plugin_err("getroute error gave no 'code'? '%.*s'",
|
|
|
|
error->end - error->start, buf + error->start);
|
2019-01-15 11:04:07 +01:00
|
|
|
|
2019-02-01 04:03:27 +01:00
|
|
|
/* Strange errors from getroute should be forwarded. */
|
|
|
|
if (code != PAY_ROUTE_NOT_FOUND)
|
|
|
|
return forward_error(cmd, buf, error, pc);
|
2019-01-15 11:04:08 +01:00
|
|
|
|
2019-02-01 04:03:27 +01:00
|
|
|
return next_routehint(cmd, pc);
|
2019-01-15 11:04:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Deep copy of excludes array. */
|
|
|
|
static const char **dup_excludes(const tal_t *ctx, const char **excludes)
|
|
|
|
{
|
|
|
|
const char **ret = tal_dup_arr(ctx, const char *,
|
|
|
|
excludes, tal_count(excludes), 0);
|
|
|
|
for (size_t i = 0; i < tal_count(ret); i++)
|
|
|
|
ret[i] = tal_strdup(ret, excludes[i]);
|
|
|
|
return ret;
|
2019-01-15 05:14:27 +01:00
|
|
|
}
|
|
|
|
|
2019-01-15 05:16:27 +01:00
|
|
|
static struct command_result *start_pay_attempt(struct command *cmd,
|
2019-01-15 11:04:08 +01:00
|
|
|
struct pay_command *pc,
|
|
|
|
const char *fmt, ...)
|
2019-01-15 05:16:27 +01:00
|
|
|
{
|
|
|
|
char *exclude;
|
2019-02-21 04:45:44 +01:00
|
|
|
struct amount_msat msat;
|
2019-01-15 11:04:01 +01:00
|
|
|
const char *dest;
|
|
|
|
size_t max_hops = ROUTING_MAX_HOPS;
|
|
|
|
u32 cltv;
|
2019-02-21 04:45:44 +01:00
|
|
|
struct pay_attempt *attempt;
|
2019-01-15 11:04:08 +01:00
|
|
|
va_list ap;
|
2019-02-21 04:45:44 +01:00
|
|
|
size_t n;
|
|
|
|
|
|
|
|
n = tal_count(pc->ps->attempts);
|
|
|
|
tal_resize(&pc->ps->attempts, n+1);
|
|
|
|
attempt = &pc->ps->attempts[n];
|
2019-01-15 11:04:07 +01:00
|
|
|
|
2019-01-15 11:04:08 +01:00
|
|
|
va_start(ap, fmt);
|
2019-02-21 04:45:44 +01:00
|
|
|
attempt->start = time_now();
|
2019-01-15 11:04:07 +01:00
|
|
|
/* Mark it unfinished */
|
2019-02-21 04:45:44 +01:00
|
|
|
attempt->end.ts.tv_sec = -1;
|
|
|
|
attempt->excludes = dup_excludes(pc->ps, pc->excludes);
|
|
|
|
attempt->route = NULL;
|
|
|
|
attempt->failure = NULL;
|
|
|
|
attempt->result = NULL;
|
|
|
|
attempt->why = tal_vfmt(pc->ps, fmt, ap);
|
2019-01-15 11:04:08 +01:00
|
|
|
va_end(ap);
|
|
|
|
|
2019-01-15 11:04:07 +01:00
|
|
|
/* routehint set below. */
|
2019-01-15 05:16:27 +01:00
|
|
|
|
|
|
|
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 = "";
|
|
|
|
|
2019-01-15 11:04:01 +01:00
|
|
|
/* If we have a routehint, try that first; we need to do extra
|
|
|
|
* checks that it meets our criteria though. */
|
2019-02-01 04:03:27 +01:00
|
|
|
if (pc->current_routehint) {
|
2019-02-21 04:45:44 +01:00
|
|
|
attempt->routehint = tal_steal(pc->ps, pc->current_routehint);
|
|
|
|
if (!route_msatoshi(&msat, pc->msat,
|
|
|
|
attempt->routehint,
|
|
|
|
tal_count(attempt->routehint))) {
|
|
|
|
attempt_failed_fmt(pc,
|
|
|
|
"{ 'message': 'Routehint absurd fee' }");
|
|
|
|
return next_routehint(cmd, pc);
|
|
|
|
}
|
2019-01-15 11:04:01 +01:00
|
|
|
dest = type_to_string(tmpctx, struct pubkey,
|
2019-02-21 04:45:44 +01:00
|
|
|
&attempt->routehint[0].pubkey);
|
|
|
|
max_hops -= tal_count(attempt->routehint);
|
2019-01-15 11:04:01 +01:00
|
|
|
cltv = route_cltv(pc->final_cltv,
|
2019-02-21 04:45:44 +01:00
|
|
|
attempt->routehint,
|
|
|
|
tal_count(attempt->routehint));
|
2019-01-15 11:04:01 +01:00
|
|
|
} else {
|
2019-02-21 04:45:44 +01:00
|
|
|
msat = pc->msat;
|
2019-01-15 11:04:01 +01:00
|
|
|
dest = pc->dest;
|
|
|
|
cltv = pc->final_cltv;
|
2019-02-21 04:45:44 +01:00
|
|
|
attempt->routehint = NULL;
|
2019-01-15 11:04:01 +01:00
|
|
|
}
|
|
|
|
|
2019-01-15 05:16:27 +01:00
|
|
|
/* OK, ask for route to destination */
|
2019-01-15 11:04:07 +01:00
|
|
|
return send_outreq(cmd, "getroute", getroute_done, getroute_error, pc,
|
2019-01-15 05:16:27 +01:00
|
|
|
"'id': '%s',"
|
2019-02-21 04:45:44 +01:00
|
|
|
"'msatoshi': '%s',"
|
2019-01-15 05:22:27 +01:00
|
|
|
"'cltv': %u,"
|
2019-01-15 11:04:01 +01:00
|
|
|
"'maxhops': %zu,"
|
2019-01-15 05:16:27 +01:00
|
|
|
"'riskfactor': %f%s",
|
2019-02-21 04:45:44 +01:00
|
|
|
dest,
|
|
|
|
type_to_string(tmpctx, struct amount_msat, &msat),
|
|
|
|
cltv, max_hops, pc->riskfactor, exclude);
|
2019-01-15 05:16:27 +01:00
|
|
|
}
|
|
|
|
|
2019-01-15 11:04:07 +01:00
|
|
|
/* BOLT #7:
|
|
|
|
*
|
|
|
|
* If a route is computed by simply routing to the intended recipient and
|
|
|
|
* summing the `cltv_expiry_delta`s, then it's possible for intermediate nodes
|
|
|
|
* to guess their position in the route. Knowing the CLTV of the HTLC, the
|
|
|
|
* surrounding network topology, and the `cltv_expiry_delta`s gives an
|
|
|
|
* attacker a way to guess the intended recipient. Therefore, it's highly
|
|
|
|
* desirable to add a random offset to the CLTV that the intended recipient
|
|
|
|
* will receive, which bumps all CLTVs along the route.
|
|
|
|
*
|
|
|
|
* In order to create a plausible offset, the origin node MAY start a limited
|
|
|
|
* random walk on the graph, starting from the intended recipient and summing
|
|
|
|
* the `cltv_expiry_delta`s, and use the resulting sum as the offset. This
|
|
|
|
* effectively creates a _shadow route extension_ to the actual route and
|
|
|
|
* provides better protection against this attack vector than simply picking a
|
|
|
|
* random offset would.
|
|
|
|
*/
|
|
|
|
static struct command_result *shadow_route(struct command *cmd,
|
|
|
|
struct pay_command *pc);
|
|
|
|
|
|
|
|
static struct command_result *add_shadow_route(struct command *cmd,
|
|
|
|
const char *buf,
|
|
|
|
const jsmntok_t *result,
|
|
|
|
struct pay_command *pc)
|
|
|
|
{
|
|
|
|
/* Use reservoir sampling across the capable channels. */
|
|
|
|
const jsmntok_t *channels = json_get_member(buf, result, "channels");
|
2019-01-17 03:12:13 +01:00
|
|
|
const jsmntok_t *chan, *best = NULL;
|
|
|
|
size_t i;
|
2019-01-15 11:04:07 +01:00
|
|
|
u64 sample;
|
|
|
|
u32 cltv, best_cltv;
|
|
|
|
|
2019-01-17 03:12:13 +01:00
|
|
|
json_for_each_arr(i, chan, channels) {
|
2019-02-21 04:45:44 +01:00
|
|
|
struct amount_sat sat;
|
2019-02-21 03:39:51 +01:00
|
|
|
u64 v;
|
2019-01-15 11:04:07 +01:00
|
|
|
|
2019-02-21 04:45:44 +01:00
|
|
|
json_to_sat(buf, json_get_member(buf, chan, "satoshis"), &sat);
|
|
|
|
if (amount_msat_greater_sat(pc->msat, sat))
|
2019-01-15 11:04:07 +01:00
|
|
|
continue;
|
|
|
|
|
|
|
|
/* Don't use if total would exceed 1/4 of our time allowance. */
|
|
|
|
json_to_number(buf, json_get_member(buf, chan, "delay"), &cltv);
|
|
|
|
if ((pc->final_cltv + cltv) * 4 > pc->maxdelay)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
v = pseudorand(UINT64_MAX);
|
|
|
|
if (!best || v > sample) {
|
|
|
|
best = chan;
|
|
|
|
best_cltv = cltv;
|
|
|
|
sample = v;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-15 11:04:07 +01:00
|
|
|
if (!best) {
|
|
|
|
tal_append_fmt(&pc->ps->shadow,
|
|
|
|
"No suitable channels found to %s. ",
|
2019-01-16 21:37:11 +01:00
|
|
|
pc->shadow_dest);
|
2019-01-15 11:04:08 +01:00
|
|
|
return start_pay_attempt(cmd, pc, "Initial attempt");
|
2019-01-15 11:04:07 +01:00
|
|
|
}
|
2019-01-15 11:04:07 +01:00
|
|
|
|
|
|
|
pc->final_cltv += best_cltv;
|
2019-01-16 21:37:11 +01:00
|
|
|
pc->shadow_dest = json_strdup(pc, buf,
|
|
|
|
json_get_member(buf, best, "destination"));
|
2019-01-15 11:04:07 +01:00
|
|
|
tal_append_fmt(&pc->ps->shadow,
|
2019-01-16 21:37:11 +01:00
|
|
|
"Added %u cltv delay for shadow to %s. ",
|
|
|
|
best_cltv, pc->shadow_dest);
|
2019-01-15 11:04:07 +01:00
|
|
|
return shadow_route(cmd, pc);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct command_result *shadow_route(struct command *cmd,
|
|
|
|
struct pay_command *pc)
|
|
|
|
{
|
|
|
|
if (pseudorand(2) == 0)
|
2019-01-15 11:04:08 +01:00
|
|
|
return start_pay_attempt(cmd, pc, "Initial attempt");
|
2019-01-15 11:04:07 +01:00
|
|
|
|
|
|
|
return send_outreq(cmd, "listchannels",
|
|
|
|
add_shadow_route, forward_error, pc,
|
2019-01-16 21:37:11 +01:00
|
|
|
"'source' : '%s'", pc->shadow_dest);
|
2019-01-15 11:04:07 +01:00
|
|
|
}
|
|
|
|
|
2019-01-15 05:18:27 +01:00
|
|
|
/* gossipd doesn't know much about the current state of channels; here we
|
|
|
|
* manually exclude peers which are disconnected and channels which lack
|
|
|
|
* current capacity (it will eliminate those without total capacity). */
|
|
|
|
static struct command_result *listpeers_done(struct command *cmd,
|
|
|
|
const char *buf,
|
|
|
|
const jsmntok_t *result,
|
|
|
|
struct pay_command *pc)
|
|
|
|
{
|
2019-01-17 03:12:13 +01:00
|
|
|
const jsmntok_t *peers, *peer;
|
|
|
|
size_t i;
|
2019-01-15 11:04:07 +01:00
|
|
|
char *mods = tal_strdup(tmpctx, "");
|
2019-01-15 05:18:27 +01:00
|
|
|
|
2019-01-17 03:12:13 +01:00
|
|
|
peers = json_get_member(buf, result, "peers");
|
|
|
|
if (!peers)
|
2019-01-15 05:18:27 +01:00
|
|
|
plugin_err("listpeers gave no 'peers'? '%.*s'",
|
|
|
|
result->end - result->start, buf);
|
|
|
|
|
2019-01-17 03:12:13 +01:00
|
|
|
json_for_each_arr(i, peer, peers) {
|
|
|
|
const jsmntok_t *chans, *chan;
|
2019-01-15 05:18:27 +01:00
|
|
|
bool connected;
|
2019-01-17 03:12:13 +01:00
|
|
|
size_t j;
|
2019-01-15 05:18:27 +01:00
|
|
|
|
|
|
|
json_to_bool(buf, json_get_member(buf, peer, "connected"),
|
|
|
|
&connected);
|
2019-01-17 03:12:13 +01:00
|
|
|
chans = json_get_member(buf, peer, "channels");
|
|
|
|
json_for_each_arr(j, chan, chans) {
|
2019-01-17 00:36:40 +01:00
|
|
|
const jsmntok_t *state, *scid, *dir;
|
2019-02-21 04:45:44 +01:00
|
|
|
struct amount_msat spendable;
|
2019-01-15 05:18:27 +01:00
|
|
|
|
|
|
|
/* gossipd will only consider things in state NORMAL
|
|
|
|
* anyway; we don't need to exclude others. */
|
|
|
|
state = json_get_member(buf, chan, "state");
|
|
|
|
if (!json_tok_streq(buf, state, "CHANNELD_NORMAL"))
|
|
|
|
continue;
|
|
|
|
|
2019-02-21 04:45:44 +01:00
|
|
|
json_to_msat(buf,
|
2019-01-17 00:36:40 +01:00
|
|
|
json_get_member(buf, chan,
|
|
|
|
"spendable_msatoshi"),
|
|
|
|
&spendable);
|
2019-01-15 05:18:27 +01:00
|
|
|
|
2019-02-21 04:45:44 +01:00
|
|
|
if (connected
|
|
|
|
&& amount_msat_greater_eq(spendable, pc->msat))
|
2019-01-15 05:18:27 +01:00
|
|
|
continue;
|
|
|
|
|
|
|
|
/* Exclude this disconnected or low-capacity channel */
|
|
|
|
scid = json_get_member(buf, chan, "short_channel_id");
|
|
|
|
dir = json_get_member(buf, chan, "direction");
|
|
|
|
tal_arr_expand(&pc->excludes,
|
|
|
|
tal_fmt(pc->excludes, "%.*s/%c",
|
|
|
|
scid->end - scid->start,
|
|
|
|
buf + scid->start,
|
|
|
|
buf[dir->start]));
|
2019-01-15 11:04:07 +01:00
|
|
|
|
|
|
|
tal_append_fmt(&mods,
|
2019-02-21 04:45:44 +01:00
|
|
|
"Excluded channel %s (%s, %s). ",
|
2019-01-15 11:04:07 +01:00
|
|
|
pc->excludes[tal_count(pc->excludes)-1],
|
2019-02-21 04:45:44 +01:00
|
|
|
type_to_string(tmpctx, struct amount_msat,
|
|
|
|
&spendable),
|
2019-01-15 11:04:07 +01:00
|
|
|
connected ? "connected" : "disconnected");
|
2019-01-15 05:18:27 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-15 11:04:07 +01:00
|
|
|
if (!streq(mods, ""))
|
|
|
|
pc->ps->exclusions = tal_steal(pc->ps, mods);
|
|
|
|
|
|
|
|
pc->ps->shadow = tal_strdup(pc->ps, "");
|
2019-01-15 11:04:07 +01:00
|
|
|
return shadow_route(cmd, pc);
|
2019-01-15 05:18:27 +01:00
|
|
|
}
|
|
|
|
|
2019-01-15 11:04:01 +01:00
|
|
|
/* Trim route to this length by taking from the *front* of route
|
|
|
|
* (end points to destination, so we need that bit!) */
|
|
|
|
static void trim_route(struct route_info **route, size_t n)
|
|
|
|
{
|
|
|
|
size_t remove = tal_count(*route) - n;
|
|
|
|
memmove(*route, *route + remove, sizeof(**route) * n);
|
|
|
|
tal_resize(route, n);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Make sure routehints are reasonable length, and (since we assume we
|
|
|
|
* can append), not directly to us. Note: untrusted data! */
|
|
|
|
static struct route_info **filter_routehints(struct pay_command *pc,
|
|
|
|
struct route_info **hints)
|
|
|
|
{
|
2019-01-15 11:04:07 +01:00
|
|
|
char *mods = tal_strdup(tmpctx, "");
|
|
|
|
|
2019-01-15 11:04:01 +01:00
|
|
|
for (size_t i = 0; i < tal_count(hints); i++) {
|
|
|
|
/* Trim any routehint > 10 hops */
|
|
|
|
size_t max_hops = ROUTING_MAX_HOPS / 2;
|
2019-01-15 11:04:07 +01:00
|
|
|
if (tal_count(hints[i]) > max_hops) {
|
|
|
|
tal_append_fmt(&mods,
|
|
|
|
"Trimmed routehint %zu (%zu hops) to %zu. ",
|
|
|
|
i, tal_count(hints[i]), max_hops);
|
2019-01-15 11:04:01 +01:00
|
|
|
trim_route(&hints[i], max_hops);
|
2019-01-15 11:04:07 +01:00
|
|
|
}
|
2019-01-15 11:04:01 +01:00
|
|
|
|
|
|
|
/* If we are first hop, trim. */
|
|
|
|
if (tal_count(hints[i]) > 0
|
2019-01-15 11:04:07 +01:00
|
|
|
&& pubkey_eq(&hints[i][0].pubkey, &my_id)) {
|
|
|
|
tal_append_fmt(&mods,
|
|
|
|
"Removed ourselves from routehint %zu. ",
|
|
|
|
i);
|
2019-01-15 11:04:01 +01:00
|
|
|
trim_route(&hints[i], tal_count(hints[i])-1);
|
2019-01-15 11:04:07 +01:00
|
|
|
}
|
2019-01-15 11:04:01 +01:00
|
|
|
|
|
|
|
/* If route is empty, remove altogether. */
|
|
|
|
if (tal_count(hints[i]) == 0) {
|
2019-01-15 11:04:07 +01:00
|
|
|
tal_append_fmt(&mods,
|
|
|
|
"Removed empty routehint %zu. ", i);
|
2019-01-15 11:04:01 +01:00
|
|
|
tal_arr_remove(&hints, i);
|
|
|
|
i--;
|
|
|
|
}
|
|
|
|
}
|
2019-01-15 11:04:07 +01:00
|
|
|
|
|
|
|
if (!streq(mods, ""))
|
|
|
|
pc->ps->routehint_modifications = tal_steal(pc->ps, mods);
|
|
|
|
|
2019-01-15 11:04:01 +01:00
|
|
|
return tal_steal(pc, hints);
|
|
|
|
}
|
|
|
|
|
2019-01-15 11:04:07 +01:00
|
|
|
static struct pay_status *add_pay_status(struct pay_command *pc,
|
|
|
|
const char *b11str)
|
|
|
|
{
|
|
|
|
struct pay_status *ps = tal(NULL, struct pay_status);
|
|
|
|
|
|
|
|
/* The pay_status outlives the pc, so it simply takes field ownership */
|
|
|
|
ps->dest = tal_steal(ps, pc->dest);
|
|
|
|
ps->desc = tal_steal(ps, pc->desc);
|
2019-02-21 04:45:44 +01:00
|
|
|
ps->msat = pc->msat;
|
2019-01-15 11:04:07 +01:00
|
|
|
ps->final_cltv = pc->final_cltv;
|
|
|
|
ps->bolt11 = tal_steal(ps, b11str);
|
|
|
|
ps->routehint_modifications = NULL;
|
|
|
|
ps->shadow = NULL;
|
|
|
|
ps->exclusions = NULL;
|
|
|
|
ps->attempts = tal_arr(ps, struct pay_attempt, 0);
|
|
|
|
|
|
|
|
list_add_tail(&pay_status, &ps->list);
|
|
|
|
return ps;
|
|
|
|
}
|
|
|
|
|
2019-01-15 05:14:27 +01:00
|
|
|
static struct command_result *handle_pay(struct command *cmd,
|
|
|
|
const char *buf,
|
|
|
|
const jsmntok_t *params)
|
|
|
|
{
|
2019-02-21 03:39:21 +01:00
|
|
|
struct amount_msat *msat;
|
2019-01-15 05:14:27 +01:00
|
|
|
struct bolt11 *b11;
|
|
|
|
const char *b11str;
|
|
|
|
char *fail;
|
|
|
|
double *riskfactor;
|
2019-01-15 05:16:27 +01:00
|
|
|
unsigned int *retryfor;
|
2019-01-15 05:14:27 +01:00
|
|
|
struct pay_command *pc = tal(cmd, struct pay_command);
|
|
|
|
double *maxfeepercent;
|
|
|
|
unsigned int *maxdelay;
|
2019-02-21 04:45:44 +01:00
|
|
|
struct amount_msat *exemptfee;
|
2019-01-15 05:14:27 +01:00
|
|
|
|
|
|
|
setup_locale();
|
|
|
|
|
|
|
|
if (!param(cmd, buf, params,
|
|
|
|
p_req("bolt11", param_string, &b11str),
|
2019-02-21 03:39:21 +01:00
|
|
|
p_opt("msatoshi", param_msat, &msat),
|
2019-01-15 05:14:27 +01:00
|
|
|
p_opt("description", param_string, &pc->desc),
|
2019-02-01 06:53:39 +01:00
|
|
|
p_opt_def("riskfactor", param_double, &riskfactor, 10),
|
2019-01-15 05:14:27 +01:00
|
|
|
p_opt_def("maxfeepercent", param_percent, &maxfeepercent, 0.5),
|
|
|
|
p_opt_def("retry_for", param_number, &retryfor, 60),
|
|
|
|
p_opt_def("maxdelay", param_number, &maxdelay,
|
2019-01-15 05:21:27 +01:00
|
|
|
maxdelay_default),
|
2019-02-21 04:45:44 +01:00
|
|
|
p_opt_def("exemptfee", param_msat, &exemptfee, AMOUNT_MSAT(5000)),
|
2019-01-15 05:14:27 +01:00
|
|
|
NULL))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
b11 = bolt11_decode(cmd, b11str, pc->desc, &fail);
|
|
|
|
if (!b11) {
|
|
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
|
|
"Invalid bolt11: %s", fail);
|
|
|
|
}
|
|
|
|
|
2019-01-15 05:16:27 +01:00
|
|
|
if (time_now().ts.tv_sec > b11->timestamp + b11->expiry) {
|
|
|
|
return command_fail(cmd, PAY_INVOICE_EXPIRED, "Invoice expired");
|
|
|
|
}
|
|
|
|
|
2019-02-21 03:38:35 +01:00
|
|
|
if (b11->msat) {
|
2019-02-21 03:39:21 +01:00
|
|
|
if (msat) {
|
2019-01-15 05:14:27 +01:00
|
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
|
|
"msatoshi parameter unnecessary");
|
|
|
|
}
|
2019-02-21 04:45:44 +01:00
|
|
|
pc->msat = *b11->msat;
|
2019-01-15 05:14:27 +01:00
|
|
|
} else {
|
2019-02-21 03:39:21 +01:00
|
|
|
if (!msat) {
|
2019-01-15 05:14:27 +01:00
|
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
|
|
"msatoshi parameter required");
|
|
|
|
}
|
2019-02-21 04:45:44 +01:00
|
|
|
pc->msat = *msat;
|
2019-01-15 05:14:27 +01:00
|
|
|
}
|
|
|
|
|
2019-01-15 05:19:27 +01:00
|
|
|
pc->maxfeepercent = *maxfeepercent;
|
|
|
|
pc->maxdelay = *maxdelay;
|
|
|
|
pc->exemptfee = *exemptfee;
|
2019-01-15 05:16:27 +01:00
|
|
|
pc->riskfactor = *riskfactor;
|
2019-01-15 05:22:27 +01:00
|
|
|
pc->final_cltv = b11->min_final_cltv_expiry;
|
2019-01-15 05:16:27 +01:00
|
|
|
pc->dest = type_to_string(cmd, struct pubkey, &b11->receiver_id);
|
2019-01-16 21:37:11 +01:00
|
|
|
pc->shadow_dest = tal_strdup(pc, pc->dest);
|
2019-01-15 05:14:27 +01:00
|
|
|
pc->payment_hash = type_to_string(pc, struct sha256,
|
|
|
|
&b11->payment_hash);
|
2019-01-15 05:16:27 +01:00
|
|
|
pc->stoptime = timeabs_add(time_now(), time_from_sec(*retryfor));
|
|
|
|
pc->excludes = tal_arr(cmd, const char *, 0);
|
2019-01-15 11:04:07 +01:00
|
|
|
pc->ps = add_pay_status(pc, b11str);
|
2019-02-01 04:03:27 +01:00
|
|
|
/* We try first without using routehint */
|
|
|
|
pc->current_routehint = NULL;
|
2019-01-15 11:04:07 +01:00
|
|
|
pc->routehints = filter_routehints(pc, b11->routes);
|
2019-01-15 11:04:08 +01:00
|
|
|
pc->expensive_route = NULL;
|
2019-01-15 05:14:27 +01:00
|
|
|
|
2019-01-15 05:18:27 +01:00
|
|
|
/* Get capacities of local channels. */
|
|
|
|
return send_outreq(cmd, "listpeers", listpeers_done, forward_error, pc,
|
2019-01-17 00:37:01 +01:00
|
|
|
/* gcc doesn't like zero-length format strings! */
|
2019-01-15 05:18:27 +01:00
|
|
|
" ");
|
2019-01-15 05:14:27 +01:00
|
|
|
}
|
|
|
|
|
2019-01-15 11:04:07 +01:00
|
|
|
/* FIXME: Add this to ccan/time? */
|
|
|
|
#define UTC_TIMELEN (sizeof("YYYY-mm-ddTHH:MM:SS.nnnZ"))
|
|
|
|
static void utc_timestring(const struct timeabs *time, char str[UTC_TIMELEN])
|
|
|
|
{
|
|
|
|
char iso8601_msec_fmt[sizeof("YYYY-mm-ddTHH:MM:SS.%03dZ")];
|
|
|
|
|
|
|
|
strftime(iso8601_msec_fmt, sizeof(iso8601_msec_fmt), "%FT%T.%%03dZ",
|
|
|
|
gmtime(&time->ts.tv_sec));
|
|
|
|
snprintf(str, UTC_TIMELEN, iso8601_msec_fmt,
|
|
|
|
(int) time->ts.tv_nsec / 1000000);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void add_attempt(char **ret,
|
|
|
|
const struct pay_status *ps,
|
|
|
|
const struct pay_attempt *attempt)
|
|
|
|
{
|
|
|
|
char timestr[UTC_TIMELEN];
|
|
|
|
|
|
|
|
utc_timestring(&attempt->start, timestr);
|
|
|
|
|
2019-01-15 11:04:08 +01:00
|
|
|
tal_append_fmt(ret, "{ 'strategy': '%s',"
|
|
|
|
" 'start_time': '%s',"
|
2019-01-15 11:04:07 +01:00
|
|
|
" 'age_in_seconds': %"PRIu64,
|
2019-01-15 11:04:08 +01:00
|
|
|
attempt->why,
|
2019-01-15 11:04:07 +01:00
|
|
|
timestr,
|
|
|
|
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)));
|
|
|
|
}
|
|
|
|
if (tal_count(attempt->routehint)) {
|
|
|
|
tal_append_fmt(ret, ", 'routehint': [");
|
|
|
|
for (size_t i = 0; i < tal_count(attempt->routehint); i++) {
|
|
|
|
tal_append_fmt(ret, "%s{"
|
|
|
|
" 'id': '%s',"
|
|
|
|
" 'channel': '%s',"
|
2019-02-21 04:45:44 +01:00
|
|
|
" 'fee_base_msat': %u,"
|
|
|
|
" 'fee_proportional_millionths': %u,"
|
|
|
|
" 'cltv_expiry_delta': %u }",
|
2019-01-15 11:04:07 +01:00
|
|
|
i == 0 ? "" : ", ",
|
|
|
|
type_to_string(tmpctx, struct pubkey,
|
|
|
|
&attempt->routehint[i].pubkey),
|
|
|
|
type_to_string(tmpctx,
|
|
|
|
struct short_channel_id,
|
|
|
|
&attempt->routehint[i].short_channel_id),
|
2019-02-21 04:45:44 +01:00
|
|
|
attempt->routehint[i].fee_base_msat,
|
|
|
|
attempt->routehint[i].fee_proportional_millionths,
|
|
|
|
attempt->routehint[i].cltv_expiry_delta);
|
2019-01-15 11:04:07 +01:00
|
|
|
}
|
|
|
|
tal_append_fmt(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, "]");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (attempt->route)
|
|
|
|
tal_append_fmt(ret, ", 'route': %s", attempt->route);
|
|
|
|
|
|
|
|
if (attempt->failure)
|
|
|
|
tal_append_fmt(ret, ", 'failure': %s", attempt->failure);
|
|
|
|
|
|
|
|
if (attempt->result)
|
|
|
|
tal_append_fmt(ret, ", 'success': %s", attempt->result);
|
|
|
|
|
|
|
|
tal_append_fmt(ret, "}");
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct command_result *handle_paystatus(struct command *cmd,
|
|
|
|
const char *buf,
|
|
|
|
const jsmntok_t *params)
|
|
|
|
{
|
|
|
|
struct pay_status *ps;
|
|
|
|
const char *b11str;
|
|
|
|
char *ret;
|
|
|
|
bool some = false;
|
|
|
|
|
|
|
|
if (!param(cmd, buf, params,
|
|
|
|
p_opt("bolt11", param_string, &b11str),
|
|
|
|
NULL))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
ret = tal_fmt(cmd, "{ '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", "
|
2019-02-21 04:45:44 +01:00
|
|
|
" 'amount_msat': '%s', "
|
2019-01-15 11:04:07 +01:00
|
|
|
" 'destination': '%s'",
|
2019-02-21 04:45:44 +01:00
|
|
|
ps->bolt11,
|
2019-02-21 04:45:56 +01:00
|
|
|
ps->msat.millisatoshis, /* Raw: JSON */
|
2019-02-21 04:45:44 +01:00
|
|
|
type_to_string(tmpctx, struct amount_msat,
|
|
|
|
&ps->msat), ps->dest);
|
2019-01-15 11:04:07 +01:00
|
|
|
if (ps->desc)
|
|
|
|
tal_append_fmt(&ret, ", 'description': '%s'", ps->desc);
|
|
|
|
if (ps->routehint_modifications)
|
|
|
|
tal_append_fmt(&ret, ", 'routehint_modifications': '%s'",
|
|
|
|
ps->routehint_modifications);
|
|
|
|
if (ps->shadow && !streq(ps->shadow, ""))
|
|
|
|
tal_append_fmt(&ret, ", 'shadow': '%s'", ps->shadow);
|
|
|
|
if (ps->exclusions)
|
|
|
|
tal_append_fmt(&ret, ", 'local_exclusions': '%s'",
|
|
|
|
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, "] }");
|
|
|
|
}
|
|
|
|
tal_append_fmt(&ret, "] }");
|
|
|
|
|
|
|
|
return command_success(cmd, ret);
|
|
|
|
}
|
|
|
|
|
2019-01-15 05:20:27 +01:00
|
|
|
static void init(struct plugin_conn *rpc)
|
|
|
|
{
|
|
|
|
const char *field;
|
|
|
|
|
|
|
|
field = rpc_delve(tmpctx, "getinfo", "", rpc, ".id");
|
|
|
|
if (!pubkey_from_hexstr(field, strlen(field), &my_id))
|
|
|
|
plugin_err("getinfo didn't contain valid id: '%s'", field);
|
2019-01-15 05:21:27 +01:00
|
|
|
|
|
|
|
field = rpc_delve(tmpctx, "listconfigs",
|
|
|
|
"'config': 'max-locktime-blocks'",
|
|
|
|
rpc, ".max-locktime-blocks");
|
|
|
|
maxdelay_default = atoi(field);
|
2019-01-15 05:20:27 +01:00
|
|
|
}
|
|
|
|
|
2019-01-15 05:14:27 +01:00
|
|
|
static const struct plugin_command commands[] = { {
|
2019-01-15 11:04:07 +01:00
|
|
|
"pay",
|
2019-02-21 04:45:44 +01:00
|
|
|
"Send payment specified by {bolt11} with {amount}",
|
2019-01-15 05:14:27 +01:00
|
|
|
"Try to send a payment, retrying {retry_for} seconds before giving up",
|
|
|
|
handle_pay
|
2019-01-15 11:04:07 +01:00
|
|
|
}, {
|
|
|
|
"paystatus",
|
|
|
|
"Detail status of attempts to pay {bolt11}, or all",
|
|
|
|
"Covers both old payments and current ones.",
|
|
|
|
handle_paystatus
|
2019-01-15 05:14:27 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
int main(int argc, char *argv[])
|
|
|
|
{
|
2019-01-15 05:20:27 +01:00
|
|
|
plugin_main(argv, init, commands, ARRAY_SIZE(commands));
|
2019-01-15 05:14:27 +01:00
|
|
|
}
|