mirror of
https://github.com/aljazceru/lightning.git
synced 2025-12-24 01:24:26 +01:00
We sanitize the routes: firstly, we assume appending so eliminate the first hop if the route points straight to us. Secondly, eliminate empty hints. Thirdly, trim overlong hints. Then we just use the first route hint. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
493 lines
14 KiB
C
493 lines
14 KiB
C
#include <ccan/array_size/array_size.h>
|
|
#include <ccan/intmap/intmap.h>
|
|
#include <ccan/tal/str/str.h>
|
|
#include <ccan/time/time.h>
|
|
#include <common/bolt11.h>
|
|
#include <common/type_to_string.h>
|
|
#include <gossipd/gossip_constants.h>
|
|
#include <plugins/libplugin.h>
|
|
|
|
/* Public key of this node. */
|
|
static struct pubkey my_id;
|
|
static unsigned int maxdelay_default;
|
|
|
|
struct pay_attempt {
|
|
const char *route;
|
|
const char *failure;
|
|
};
|
|
|
|
struct pay_command {
|
|
/* Destination, as text */
|
|
const char *dest;
|
|
|
|
/* How much we're paying, and what riskfactor for routing. */
|
|
u64 msatoshi;
|
|
double riskfactor;
|
|
unsigned int final_cltv;
|
|
|
|
/* Limits on what routes we'll accept. */
|
|
double maxfeepercent;
|
|
unsigned int maxdelay;
|
|
u64 exemptfee;
|
|
|
|
/* Payment hash, as text. */
|
|
const char *payment_hash;
|
|
|
|
/* Description, if any. */
|
|
const char *desc;
|
|
|
|
/* Chatty description of attempts. */
|
|
struct pay_attempt *attempts;
|
|
|
|
/* Time to stop retrying. */
|
|
struct timeabs stoptime;
|
|
|
|
/* Channels which have failed us. */
|
|
const char **excludes;
|
|
|
|
/* Any routehints to use. */
|
|
struct route_info **routehints;
|
|
};
|
|
|
|
static struct command_result *start_pay_attempt(struct command *cmd,
|
|
struct pay_command *pc);
|
|
|
|
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",
|
|
tal_count(pc->attempts));
|
|
data = tal_strdup(pc, "'attempts': [ ");
|
|
for (size_t i = 0; i < tal_count(pc->attempts); i++)
|
|
tal_append_fmt(&data, "%s { 'route': %s,\n 'failure': '%s'\n }",
|
|
i == 0 ? "" : ",",
|
|
pc->attempts[i].route,
|
|
pc->attempts[i].failure);
|
|
tal_append_fmt(&data, "]");
|
|
return command_done_err(cmd, PAY_STOPPED_RETRYING, errmsg, data);
|
|
}
|
|
|
|
static struct command_result *waitsendpay_error(struct command *cmd,
|
|
const char *buf,
|
|
const jsmntok_t *error,
|
|
struct pay_command *pc)
|
|
{
|
|
struct pay_attempt *attempt;
|
|
const jsmntok_t *codetok, *scidtok, *dirtok;
|
|
int code;
|
|
|
|
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);
|
|
|
|
/* 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]));
|
|
|
|
attempt = &pc->attempts[tal_count(pc->attempts)-1];
|
|
attempt->failure = json_strdup(pc->attempts, buf, error);
|
|
|
|
if (time_after(time_now(), pc->stoptime)) {
|
|
return waitsendpay_expired(cmd, pc);
|
|
}
|
|
|
|
/* Try again. */
|
|
return start_pay_attempt(cmd, pc);
|
|
}
|
|
|
|
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",
|
|
forward_result, waitsendpay_error, pc,
|
|
"'payment_hash': '%s', 'timeout': 60",
|
|
pc->payment_hash);
|
|
}
|
|
|
|
/* Calculate how many millisatoshi we need at the start of this route
|
|
* to get msatoshi to the end. */
|
|
static u64 route_msatoshi(u64 msatoshi,
|
|
const struct route_info *route, size_t num_route)
|
|
{
|
|
for (ssize_t i = num_route - 1; i >= 0; i--) {
|
|
u64 fee;
|
|
|
|
fee = route[i].fee_base_msat;
|
|
fee += (route[i].fee_proportional_millionths * msatoshi) / 1000000;
|
|
msatoshi += fee;
|
|
}
|
|
return msatoshi;
|
|
}
|
|
|
|
/* 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++) {
|
|
tal_append_fmt(&ret, ", {"
|
|
" 'id': '%s',"
|
|
" 'channel': '%s',"
|
|
" 'msatoshi': %"PRIu64","
|
|
" '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),
|
|
/* amount to be received by *destination* */
|
|
route_msatoshi(pc->msatoshi, routehint + i + 1,
|
|
tal_count(routehint) - i - 1),
|
|
/* cltv for *destination* */
|
|
route_cltv(pc->final_cltv, routehint + i + 1,
|
|
tal_count(routehint) - i - 1));
|
|
}
|
|
/* Put ] back */
|
|
tal_append_fmt(&ret, "]");
|
|
return ret;
|
|
}
|
|
|
|
static struct command_result *getroute_done(struct command *cmd,
|
|
const char *buf,
|
|
const jsmntok_t *result,
|
|
struct pay_command *pc)
|
|
{
|
|
struct pay_attempt attempt;
|
|
const jsmntok_t *t = json_get_member(buf, result, "route");
|
|
char *json_desc;
|
|
u64 fee;
|
|
u32 delay;
|
|
double feepercent;
|
|
|
|
if (!t)
|
|
plugin_err("getroute gave no 'route'? '%.*s'",
|
|
result->end - result->start, buf);
|
|
|
|
if (!json_to_u64(buf, json_delve(buf, t, "[0].msatoshi"), &fee))
|
|
plugin_err("getroute with invalid msatoshi? '%.*s'",
|
|
result->end - result->start, buf);
|
|
fee -= pc->msatoshi;
|
|
|
|
if (!json_to_number(buf, json_delve(buf, t, "[0].delay"), &delay))
|
|
plugin_err("getroute with invalid delay? '%.*s'",
|
|
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. */
|
|
feepercent = ((double)fee) * 100.0 / ((double) pc->msatoshi);
|
|
|
|
if (fee > pc->exemptfee && feepercent > pc->maxfeepercent) {
|
|
return command_fail(cmd, PAY_ROUTE_TOO_EXPENSIVE,
|
|
"Route wanted fee of %"PRIu64" msatoshis",
|
|
fee);
|
|
}
|
|
|
|
if (delay > pc->maxdelay) {
|
|
return command_fail(cmd, PAY_ROUTE_TOO_EXPENSIVE,
|
|
"Route wanted delay of %u blocks", delay);
|
|
}
|
|
|
|
if (tal_count(pc->routehints))
|
|
attempt.route = join_routehint(pc->attempts, buf, t,
|
|
pc, pc->routehints[0]);
|
|
else
|
|
attempt.route = json_strdup(pc->attempts, buf, t);
|
|
tal_arr_expand(&pc->attempts, attempt);
|
|
|
|
if (pc->desc)
|
|
json_desc = tal_fmt(pc, ", 'description': '%s'", pc->desc);
|
|
else
|
|
json_desc = "";
|
|
|
|
return send_outreq(cmd, "sendpay", sendpay_done, forward_error, pc,
|
|
"'route': %s, 'payment_hash': '%s'%s",
|
|
attempt.route,
|
|
pc->payment_hash,
|
|
json_desc);
|
|
}
|
|
|
|
static struct command_result *start_pay_attempt(struct command *cmd,
|
|
struct pay_command *pc)
|
|
{
|
|
char *exclude;
|
|
u64 amount;
|
|
const char *dest;
|
|
size_t max_hops = ROUTING_MAX_HOPS;
|
|
u32 cltv;
|
|
|
|
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 (tal_count(pc->routehints)) {
|
|
amount = route_msatoshi(pc->msatoshi,
|
|
pc->routehints[0],
|
|
tal_count(pc->routehints[0]));
|
|
dest = type_to_string(tmpctx, struct pubkey,
|
|
&pc->routehints[0][0].pubkey);
|
|
max_hops -= tal_count(pc->routehints[0]);
|
|
cltv = route_cltv(pc->final_cltv,
|
|
pc->routehints[0],
|
|
tal_count(pc->routehints[0]));
|
|
} else {
|
|
amount = pc->msatoshi;
|
|
dest = pc->dest;
|
|
cltv = pc->final_cltv;
|
|
}
|
|
|
|
/* OK, ask for route to destination */
|
|
return send_outreq(cmd, "getroute", getroute_done, forward_error, pc,
|
|
"'id': '%s',"
|
|
"'msatoshi': %"PRIu64","
|
|
"'cltv': %u,"
|
|
"'maxhops': %zu,"
|
|
"'riskfactor': %f%s",
|
|
dest, amount, cltv, max_hops, pc->riskfactor, exclude);
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
const jsmntok_t *peer, *peers_end;
|
|
|
|
peer = json_get_member(buf, result, "peers");
|
|
if (!peer)
|
|
plugin_err("listpeers gave no 'peers'? '%.*s'",
|
|
result->end - result->start, buf);
|
|
|
|
peers_end = json_next(peer);
|
|
for (peer = peer + 1; peer < peers_end; peer = json_next(peer)) {
|
|
const jsmntok_t *chan, *chans_end;
|
|
bool connected;
|
|
|
|
json_to_bool(buf, json_get_member(buf, peer, "connected"),
|
|
&connected);
|
|
chan = json_get_member(buf, peer, "channels");
|
|
chans_end = json_next(chan);
|
|
for (chan = chan + 1; chan < chans_end; chan = json_next(chan)) {
|
|
const jsmntok_t *state, *spendable, *scid, *dir;
|
|
u64 capacity;
|
|
|
|
/* 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;
|
|
|
|
spendable = json_get_member(buf, chan,
|
|
"spendable_msatoshi");
|
|
json_to_u64(buf, spendable, &capacity);
|
|
|
|
if (connected && capacity >= pc->msatoshi)
|
|
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]));
|
|
}
|
|
}
|
|
|
|
return start_pay_attempt(cmd, pc);
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
for (size_t i = 0; i < tal_count(hints); i++) {
|
|
/* Trim any routehint > 10 hops */
|
|
size_t max_hops = ROUTING_MAX_HOPS / 2;
|
|
if (tal_count(hints[i]) > max_hops)
|
|
trim_route(&hints[i], max_hops);
|
|
|
|
/* If we are first hop, trim. */
|
|
if (tal_count(hints[i]) > 0
|
|
&& pubkey_eq(&hints[i][0].pubkey, &my_id))
|
|
trim_route(&hints[i], tal_count(hints[i])-1);
|
|
|
|
/* If route is empty, remove altogether. */
|
|
if (tal_count(hints[i]) == 0) {
|
|
tal_arr_remove(&hints, i);
|
|
i--;
|
|
}
|
|
}
|
|
return tal_steal(pc, hints);
|
|
}
|
|
|
|
static struct command_result *handle_pay(struct command *cmd,
|
|
const char *buf,
|
|
const jsmntok_t *params)
|
|
{
|
|
u64 *msatoshi;
|
|
struct bolt11 *b11;
|
|
const char *b11str;
|
|
char *fail;
|
|
double *riskfactor;
|
|
unsigned int *retryfor;
|
|
struct pay_command *pc = tal(cmd, struct pay_command);
|
|
double *maxfeepercent;
|
|
unsigned int *maxdelay;
|
|
u64 *exemptfee;
|
|
|
|
setup_locale();
|
|
|
|
if (!param(cmd, buf, params,
|
|
p_req("bolt11", param_string, &b11str),
|
|
p_opt("msatoshi", param_u64, &msatoshi),
|
|
p_opt("description", param_string, &pc->desc),
|
|
p_opt_def("riskfactor", param_double, &riskfactor, 1.0),
|
|
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,
|
|
maxdelay_default),
|
|
p_opt_def("exemptfee", param_u64, &exemptfee, 5000),
|
|
NULL))
|
|
return NULL;
|
|
|
|
b11 = bolt11_decode(cmd, b11str, pc->desc, &fail);
|
|
if (!b11) {
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"Invalid bolt11: %s", fail);
|
|
}
|
|
|
|
if (time_now().ts.tv_sec > b11->timestamp + b11->expiry) {
|
|
return command_fail(cmd, PAY_INVOICE_EXPIRED, "Invoice expired");
|
|
}
|
|
|
|
if (b11->msatoshi) {
|
|
if (msatoshi) {
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"msatoshi parameter unnecessary");
|
|
}
|
|
pc->msatoshi = *b11->msatoshi;
|
|
} else {
|
|
if (!msatoshi) {
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"msatoshi parameter required");
|
|
}
|
|
pc->msatoshi = *msatoshi;
|
|
}
|
|
|
|
pc->routehints = filter_routehints(pc, b11->routes);
|
|
pc->maxfeepercent = *maxfeepercent;
|
|
pc->maxdelay = *maxdelay;
|
|
pc->exemptfee = *exemptfee;
|
|
pc->riskfactor = *riskfactor;
|
|
pc->final_cltv = b11->min_final_cltv_expiry;
|
|
pc->dest = type_to_string(cmd, struct pubkey, &b11->receiver_id);
|
|
pc->payment_hash = type_to_string(pc, struct sha256,
|
|
&b11->payment_hash);
|
|
pc->stoptime = timeabs_add(time_now(), time_from_sec(*retryfor));
|
|
pc->attempts = tal_arr(cmd, struct pay_attempt, 0);
|
|
pc->excludes = tal_arr(cmd, const char *, 0);
|
|
|
|
/* Get capacities of local channels. */
|
|
return send_outreq(cmd, "listpeers", listpeers_done, forward_error, pc,
|
|
" ");
|
|
}
|
|
|
|
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);
|
|
|
|
field = rpc_delve(tmpctx, "listconfigs",
|
|
"'config': 'max-locktime-blocks'",
|
|
rpc, ".max-locktime-blocks");
|
|
maxdelay_default = atoi(field);
|
|
}
|
|
|
|
static const struct plugin_command commands[] = { {
|
|
"pay2",
|
|
"Send payment specified by {bolt11} with {msatoshi}",
|
|
"Try to send a payment, retrying {retry_for} seconds before giving up",
|
|
handle_pay
|
|
}
|
|
};
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
plugin_main(argv, init, commands, ARRAY_SIZE(commands));
|
|
}
|