You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

696 lines
19 KiB

#include "pay.h"
#include "payalgo.h"
#include <ccan/crypto/siphash24/siphash24.h>
#include <ccan/list/list.h>
#include <ccan/tal/str/str.h>
#include <ccan/time/time.h>
#include <common/bolt11.h>
#include <common/timeout.h>
#include <common/type_to_string.h>
#include <gossipd/gen_gossip_wire.h>
#include <gossipd/routing.h>
#include <lightningd/json.h>
#include <lightningd/jsonrpc.h>
#include <lightningd/jsonrpc_errors.h>
#include <lightningd/lightningd.h>
#include <lightningd/log.h>
#include <lightningd/subd.h>
#include <sodium/randombytes.h>
#include <wallet/wallet.h>
/* Record of failures. */
enum pay_failure_type {
FAIL_UNPARSEABLE_ONION,
FAIL_PAYMENT_REPLY
};
/* A payment failure. */
struct pay_failure {
/* Part of pay_failures list of struct pay */
struct list_node list;
/* The type of payment failure */
enum pay_failure_type type;
/* A tal_arr of route hops, whose parent is
* this struct */
struct route_hop *route;
/* The raw onion reply, if TYPE_UNPARSEABLE_ONION, a
* tal_arr whose parent is this struct */
const u8 *onionreply;
/* The routing failure, if TYPE_PAYMENT_REPLY, a tal
* object whose parent is this struct */
struct routing_failure *routing_failure;
};
/* Output a pay failure */
static void
json_add_failure(struct json_result *r, char const *n,
const struct pay_failure *f)
{
struct routing_failure *rf;
json_object_start(r, n);
switch (f->type) {
case FAIL_UNPARSEABLE_ONION:
json_add_string(r, "type", "FAIL_UNPARSEABLE_ONION");
json_add_hex(r, "onionreply", f->onionreply,
tal_len(f->onionreply));
json_add_route(r, "route", f->route, tal_count(f->route));
break;
case FAIL_PAYMENT_REPLY:
rf = f->routing_failure;
json_add_string(r, "type", "FAIL_PAYMENT_REPLY");
json_add_num(r, "erring_index", rf->erring_index);
json_add_num(r, "failcode", (unsigned) rf->failcode);
json_add_hex(r, "erring_node",
&rf->erring_node,
sizeof(rf->erring_node));
json_add_short_channel_id(r, "erring_channel",
&rf->erring_channel);
if (rf->channel_update)
json_add_hex(r, "channel_update",
rf->channel_update,
tal_len(rf->channel_update));
json_add_route(r, "route", f->route, tal_count(f->route));
break;
}
json_object_end(r);
}
/* Output an array of payment failures. */
static void
json_add_failures(struct json_result *r, char const *n,
const struct list_head *fs)
{
struct pay_failure *f;
json_array_start(r, n);
list_for_each(fs, f, list) {
json_add_failure(r, NULL, f);
}
json_array_end(r);
}
/* Pay command */
struct pay {
/* Parent command. */
struct command *cmd;
/* Bolt11 details */
struct sha256 payment_hash;
struct pubkey receiver_id;
struct timeabs expiry;
u32 min_final_cltv_expiry;
/* Command details */
u64 msatoshi;
double riskfactor;
double maxfeepercent;
/* Number of getroute and sendpay tries */
unsigned int getroute_tries;
unsigned int sendpay_tries;
/* Current fuzz we pass into getroute. */
double fuzz;
/* Parent of the current pay attempt. This object is
* freed, then allocated at the start of each pay
* attempt to ensure no leaks across long pay attempts */
char *try_parent;
/* Current route being attempted. */
struct route_hop *route;
/* List of failures to pay. */
struct list_head pay_failures;
/* Whether we are attempting payment or not. */
bool in_sendpay;
};
static struct routing_failure *
dup_routing_failure(const tal_t *ctx, const struct routing_failure *fail)
{
struct routing_failure *nobj = tal(ctx, struct routing_failure);
nobj->erring_index = fail->erring_index;
nobj->failcode = fail->failcode;
nobj->erring_node = fail->erring_node;
nobj->erring_channel = fail->erring_channel;
if (fail->channel_update)
nobj->channel_update = tal_dup_arr(nobj, u8,
fail->channel_update,
tal_count(fail->channel_update),
0);
else
nobj->channel_update = NULL;
return nobj;
}
/* Add a pay_failure from a sendpay_result */
static void
add_pay_failure(struct pay *pay,
const struct sendpay_result *r)
{
struct pay_failure *f = tal(pay, struct pay_failure);
/* Append to tail */
list_add_tail(&pay->pay_failures, &f->list);
switch (r->errorcode) {
case PAY_UNPARSEABLE_ONION:
f->type = FAIL_UNPARSEABLE_ONION;
f->onionreply = tal_dup_arr(f, u8, r->onionreply,
tal_count(r->onionreply), 0);
break;
case PAY_TRY_OTHER_ROUTE:
f->type = FAIL_PAYMENT_REPLY;
f->routing_failure = dup_routing_failure(f,
r->routing_failure);
break;
/* All other errors are disallowed */
default:
abort();
}
/* Grab the route */
f->route = tal_steal(f, pay->route);
pay->route = NULL;
}
static void
json_pay_success(struct pay *pay,
const struct sendpay_result *r)
{
struct command *cmd = pay->cmd;
struct json_result *response;
response = new_json_result(cmd);
json_object_start(response, NULL);
json_add_payment_fields(response, r->payment);
json_add_num(response, "getroute_tries", pay->getroute_tries);
json_add_num(response, "sendpay_tries", pay->sendpay_tries);
json_add_route(response, "route",
pay->route, tal_count(pay->route));
json_add_failures(response, "failures", &pay->pay_failures);
json_object_end(response);
command_success(cmd, response);
}
static void json_pay_failure(struct pay *pay,
const struct sendpay_result *r)
{
struct json_result *data = NULL;
const char *msg = NULL;
struct routing_failure *fail;
assert(!r->succeeded);
data = new_json_result(pay);
switch (r->errorcode) {
case PAY_IN_PROGRESS:
json_object_start(data, NULL);
json_add_num(data, "getroute_tries", pay->getroute_tries);
json_add_num(data, "sendpay_tries", pay->sendpay_tries);
json_add_payment_fields(data, r->payment);
json_add_failures(data, "failures", &pay->pay_failures);
json_object_end(data);
msg = r->details;
break;
case PAY_RHASH_ALREADY_USED:
case PAY_STOPPED_RETRYING:
json_object_start(data, NULL);
json_add_num(data, "getroute_tries", pay->getroute_tries);
json_add_num(data, "sendpay_tries", pay->sendpay_tries);
json_add_failures(data, "failures", &pay->pay_failures);
json_object_end(data);
msg = r->details;
break;
case PAY_UNPARSEABLE_ONION:
/* Impossible case */
abort();
break;
case PAY_DESTINATION_PERM_FAIL:
fail = r->routing_failure;
json_object_start(data, NULL);
json_add_num(data, "erring_index",
fail->erring_index);
json_add_num(data, "failcode",
(unsigned) fail->failcode);
json_add_hex(data, "erring_node",
&fail->erring_node,
sizeof(fail->erring_node));
json_add_short_channel_id(data, "erring_channel",
&fail->erring_channel);
if (fail->channel_update)
json_add_hex(data, "channel_update",
fail->channel_update,
tal_len(fail->channel_update));
json_add_failures(data, "failures", &pay->pay_failures);
json_object_end(data);
assert(r->details != NULL);
msg = tal_fmt(pay,
"failed: %s (%s)",
onion_type_name(fail->failcode),
r->details);
break;
case PAY_TRY_OTHER_ROUTE:
/* Impossible case */
abort();
break;
}
assert(msg);
command_fail_detailed(pay->cmd, r->errorcode, data, "%s", msg);
}
/* Determine if we should delay before retrying. Return a reason
* string, or NULL if we will not retry */
static const char *should_delay_retry(const tal_t *ctx,
const struct sendpay_result *r)
{
/* The routing failures WIRE_EXPIRY_TOO_FAR, WIRE_EXPIRY_TOO_SOON,
* and WIRE_FINAL_EXPIRY_TOO_SOON may arise due to disagreement
* between the peers about what the block heights are. So
* delay for those before retrying. */
if (!r->succeeded && r->errorcode == PAY_TRY_OTHER_ROUTE) {
switch (r->routing_failure->failcode) {
case WIRE_EXPIRY_TOO_FAR:
case WIRE_EXPIRY_TOO_SOON:
case WIRE_FINAL_EXPIRY_TOO_SOON:
return tal_fmt(ctx,
"Possible blockheight disagreement "
"(%s from peer)",
onion_type_name(r->routing_failure->failcode));
default:
/* Do nothing */ ;
}
}
return NULL;
}
/* Start a payment attempt. */
static bool json_pay_try(struct pay *pay);
/* Used when delaying. */
static void do_pay_try(struct pay *pay)
{
log_info(pay->cmd->ld->log, "pay(%p): Try another route", pay);
json_pay_try(pay);
}
/* Call when sendpay returns to us. */
static void json_pay_sendpay_resolve(const struct sendpay_result *r,
void *vpay)
{
struct pay *pay = (struct pay *) vpay;
char const *why;
pay->in_sendpay = false;
/* If we succeed, hurray */
if (r->succeeded) {
log_info(pay->cmd->ld->log, "pay(%p): Success", pay);
json_pay_success(pay, r);
return;
}
/* We can retry only if it is one of the retryable errors
* below. If it is not, fail now. */
if (r->errorcode != PAY_UNPARSEABLE_ONION &&
r->errorcode != PAY_TRY_OTHER_ROUTE) {
log_info(pay->cmd->ld->log, "pay(%p): Failed, reporting to caller", pay);
json_pay_failure(pay, r);
return;
}
add_pay_failure(pay, r);
/* Should retry here, question is whether to retry now or later */
why = should_delay_retry(pay->try_parent, r);
if (why) {
/* We have some reason to delay retrying. */
/* Clear previous try memory. */
pay->try_parent = tal_free(pay->try_parent);
pay->try_parent = tal(pay, char);
log_info(pay->cmd->ld->log,
"pay(%p): Delay before retry: %s", pay, why);
/* Delay for 3 seconds if needed. FIXME: random
* exponential backoff */
new_reltimer(&pay->cmd->ld->timers, pay->try_parent,
time_from_sec(3),
&do_pay_try, pay);
} else
do_pay_try(pay);
}
/* Generates a string describing the route. Route should be a
* tal_arr */
static char const *stringify_route(const tal_t *ctx, struct route_hop *route)
{
size_t i;
char *rv = tal_strdup(ctx, "us");
for (i = 0; i < tal_count(route); ++i)
tal_append_fmt(&rv, " -> %s (%"PRIu32"msat, %"PRIu32"blk) -> %s",
type_to_string(ctx, struct short_channel_id, &route[i].channel_id),
route[i].amount, route[i].delay,
type_to_string(ctx, struct pubkey, &route[i].nodeid));
return rv;
}
static void log_route(struct pay *pay, struct route_hop *route)
{
log_info(pay->cmd->ld->log, "pay(%p): sendpay via route: %s",
pay, stringify_route(tmpctx, route));
}
static void json_pay_sendpay_resume(const struct sendpay_result *r,
void *vpay)
{
struct pay *pay = (struct pay *) vpay;
bool completed = r->succeeded || r->errorcode != PAY_IN_PROGRESS;
if (completed)
/* Already completed. */
json_pay_sendpay_resolve(r, pay);
else {
/* Clear previous try memory. */
pay->try_parent = tal_free(pay->try_parent);
pay->try_parent = tal(pay, char);
/* Not yet complete? Wait for it. */
wait_payment(pay->try_parent, pay->cmd->ld, &pay->payment_hash,
json_pay_sendpay_resolve, pay);
}
}
static void json_pay_getroute_reply(struct subd *gossip UNUSED,
const u8 *reply, const int *fds UNUSED,
struct pay *pay)
{
struct route_hop *route;
u64 msatoshi_sent;
u64 fee;
double feepercent;
bool fee_too_high;
struct json_result *data;
fromwire_gossip_getroute_reply(reply, reply, &route);
if (tal_count(route) == 0) {
data = new_json_result(pay);
json_object_start(data, NULL);
json_add_num(data, "getroute_tries", pay->getroute_tries);
json_add_num(data, "sendpay_tries", pay->sendpay_tries);
json_add_failures(data, "failures", &pay->pay_failures);
json_object_end(data);
command_fail_detailed(pay->cmd, PAY_ROUTE_NOT_FOUND, data,
"Could not find a route");
return;
}
msatoshi_sent = route[0].amount;
fee = msatoshi_sent - pay->msatoshi;
/* FIXME: IEEE Double-precision floating point has only 53 bits
* of precision. Total satoshis that can ever be created is
* slightly less than 2100000000000000. Total msatoshis that
* can ever be created is 1000 times that or
* 2100000000000000000, requiring 60.865 bits of precision,
* and thus losing precision in the below. Currently, OK, as,
* payments are limited to 4294967295 msatoshi. */
feepercent = ((double) fee) * 100.0 / ((double) pay->msatoshi);
fee_too_high = (feepercent > pay->maxfeepercent);
/* compare fuzz to range */
if (fee_too_high && pay->fuzz < 0.01) {
data = new_json_result(pay);
json_object_start(data, NULL);
json_add_u64(data, "fee", fee);
json_add_double(data, "feepercent", feepercent);
json_add_u64(data, "msatoshi", pay->msatoshi);
json_add_double(data, "maxfeepercent", pay->maxfeepercent);
json_add_num(data, "getroute_tries", pay->getroute_tries);
json_add_num(data, "sendpay_tries", pay->sendpay_tries);
json_add_failures(data, "failures", &pay->pay_failures);
json_object_end(data);
command_fail_detailed(pay->cmd, PAY_ROUTE_TOO_EXPENSIVE,
data,
"Fee %"PRIu64" is %f%% "
"of payment %"PRIu64"; "
"max fee requested is %f%%",
fee, feepercent,
pay->msatoshi,
pay->maxfeepercent);
return;
}
if (fee_too_high) {
/* Retry with lower fuzz */
pay->fuzz -= 0.15;
if (pay->fuzz <= 0.0)
pay->fuzz = 0.0;
json_pay_try(pay);
return;
}
++pay->sendpay_tries;
log_route(pay, route);
assert(!pay->route);
pay->route = tal_dup_arr(pay, struct route_hop, route,
tal_count(route), 0);
pay->in_sendpay = true;
send_payment(pay->try_parent,
pay->cmd->ld, &pay->payment_hash, route,
&json_pay_sendpay_resume, pay);
}
/* Start a payment attempt. Return true if deferred,
* false if resolved now. */
static bool json_pay_try(struct pay *pay)
{
u8 *req;
struct command *cmd = pay->cmd;
struct timeabs now = time_now();
struct json_result *data;
struct siphash_seed seed;
/* If too late anyway, fail now. */
if (time_after(now, pay->expiry)) {
data = new_json_result(cmd);
json_object_start(data, NULL);
json_add_num(data, "now", now.ts.tv_sec);
json_add_num(data, "expiry", pay->expiry.ts.tv_sec);
json_add_num(data, "getroute_tries", pay->getroute_tries);
json_add_num(data, "sendpay_tries", pay->sendpay_tries);
json_add_failures(data, "failures", &pay->pay_failures);
json_object_end(data);
command_fail_detailed(cmd, PAY_INVOICE_EXPIRED, data,
"Invoice expired");
return false;
}
/* Clear previous try memory. */
pay->try_parent = tal_free(pay->try_parent);
pay->try_parent = tal(pay, char);
/* Clear route */
pay->route = tal_free(pay->route);
/* Generate random seed */
randombytes_buf(&seed, sizeof(seed));
++pay->getroute_tries;
/* FIXME: use b11->routes */
req = towire_gossip_getroute_request(pay->try_parent,
&cmd->ld->id,
&pay->receiver_id,
pay->msatoshi,
pay->riskfactor,
pay->min_final_cltv_expiry,
&pay->fuzz,
&seed);
subd_req(pay->try_parent, cmd->ld->gossip, req, -1, 0, json_pay_getroute_reply, pay);
return true;
}
static void json_pay_stop_retrying(struct pay *pay)
{
struct sendpay_result *sr;
sr = tal(pay, struct sendpay_result);
sr->succeeded = false;
if (pay->in_sendpay) {
/* Still in sendpay. Return with PAY_IN_PROGRESS */
sr->errorcode = PAY_IN_PROGRESS;
sr->payment = wallet_payment_by_hash(sr,
pay->cmd->ld->wallet,
&pay->payment_hash);
sr->details = "Stopped retrying during payment attempt; "
"continue monitoring with "
"pay or listpayments";
} else {
/* Outside sendpay, no ongoing payment */
sr->errorcode = PAY_STOPPED_RETRYING;
sr->details = "Stopped retrying, no ongoing payment";
}
json_pay_failure(pay, sr);
}
static void json_pay(struct command *cmd,
const char *buffer, const jsmntok_t *params)
{
jsmntok_t *bolt11tok, *msatoshitok, *desctok, *riskfactortok, *maxfeetok;
jsmntok_t *retryfortok;
double riskfactor = 1.0;
double maxfeepercent = 0.5;
u64 msatoshi;
struct pay *pay = tal(cmd, struct pay);
struct bolt11 *b11;
char *fail, *b11str, *desc;
unsigned int retryfor = 60;
if (!json_get_params(cmd, buffer, params,
"bolt11", &bolt11tok,
"?msatoshi", &msatoshitok,
"?description", &desctok,
"?riskfactor", &riskfactortok,
"?maxfeepercent", &maxfeetok,
"?retry_for", &retryfortok,
NULL)) {
return;
}
b11str = tal_strndup(cmd, buffer + bolt11tok->start,
bolt11tok->end - bolt11tok->start);
if (desctok)
desc = tal_strndup(cmd, buffer + desctok->start,
desctok->end - desctok->start);
else
desc = NULL;
b11 = bolt11_decode(pay, b11str, desc, &fail);
if (!b11) {
command_fail(cmd, "Invalid bolt11: %s", fail);
return;
}
pay->cmd = cmd;
pay->payment_hash = b11->payment_hash;
pay->receiver_id = b11->receiver_id;
memset(&pay->expiry, 0, sizeof(pay->expiry));
pay->expiry.ts.tv_sec = b11->timestamp + b11->expiry;
pay->min_final_cltv_expiry = b11->min_final_cltv_expiry;
if (retryfortok && !json_tok_number(buffer, retryfortok, &retryfor)) {
command_fail(cmd, "'%.*s' is not an integer",
retryfortok->end - retryfortok->start,
buffer + retryfortok->start);
return;
}
if (b11->msatoshi) {
msatoshi = *b11->msatoshi;
if (msatoshitok) {
command_fail(cmd, "msatoshi parameter unnecessary");
return;
}
} else {
if (!msatoshitok) {
command_fail(cmd, "msatoshi parameter required");
return;
}
if (!json_tok_u64(buffer, msatoshitok, &msatoshi)) {
command_fail(cmd,
"msatoshi '%.*s' is not a valid number",
msatoshitok->end-msatoshitok->start,
buffer + msatoshitok->start);
return;
}
}
pay->msatoshi = msatoshi;
if (riskfactortok
&& !json_tok_double(buffer, riskfactortok, &riskfactor)) {
command_fail(cmd, "'%.*s' is not a valid double",
riskfactortok->end - riskfactortok->start,
buffer + riskfactortok->start);
return;
}
pay->riskfactor = riskfactor * 1000;
if (maxfeetok
&& !json_tok_double(buffer, maxfeetok, &maxfeepercent)) {
command_fail(cmd, "'%.*s' is not a valid double",
maxfeetok->end - maxfeetok->start,
buffer + maxfeetok->start);
return;
}
/* Ensure it is in range 0.0 <= maxfeepercent <= 100.0 */
if (!(0.0 <= maxfeepercent)) {
command_fail(cmd, "%f maxfeepercent must be non-negative",
maxfeepercent);
return;
}
if (!(maxfeepercent <= 100.0)) {
command_fail(cmd, "%f maxfeepercent must be <= 100.0",
maxfeepercent);
return;
}
pay->maxfeepercent = maxfeepercent;
pay->getroute_tries = 0;
pay->sendpay_tries = 0;
/* Higher fuzz increases the potential fees we will pay, since
* higher fuzz makes it more likely that high-fee paths get
* selected. We start with very high fuzz, but if the
* returned route is too expensive for the given
* `maxfeepercent` we reduce the fuzz. Starting with high
* fuzz means, if the user allows high fee, we can take
* advantage of that to increase randomization and
* improve privacy somewhat. */
pay->fuzz = 0.75;
pay->try_parent = NULL;
/* Start with no route */
pay->route = NULL;
/* Start with no failures */
list_head_init(&pay->pay_failures);
pay->in_sendpay = false;
/* Initiate payment */
if (json_pay_try(pay))
command_still_pending(cmd);
else
return;
/* Set up timeout. */
new_reltimer(&cmd->ld->timers, pay, time_from_sec(retryfor),
&json_pay_stop_retrying, pay);
}
static const struct json_command pay_command = {
"pay",
json_pay,
"Send payment specified by {bolt11} with optional {msatoshi} "
"(if and only if {bolt11} does not have amount), "
"{description} (required if {bolt11} uses description hash), "
"{riskfactor} (default 1.0), "
"{maxfeepercent} (default 0.5) the maximum acceptable fee as a percentage (e.g. 0.5 => 0.5%), and "
"{retry_for} (default 60) the integer number of seconds before we stop retrying"
};
AUTODATA(json_command, &pay_command);