From 26f7014813c2956817e10aeca6bdd1306a7a18d0 Mon Sep 17 00:00:00 2001 From: ZmnSCPxj Date: Wed, 21 Mar 2018 14:18:14 +0000 Subject: [PATCH] payalgo: Add maximum delay. Fixes: #1086 --- doc/lightning-pay.7 | 18 ++++++++----- doc/lightning-pay.7.txt | 16 +++++++----- lightningd/payalgo.c | 58 +++++++++++++++++++++++++++++++++++------ 3 files changed, 72 insertions(+), 20 deletions(-) diff --git a/doc/lightning-pay.7 b/doc/lightning-pay.7 index 2cce4e182..84b1d02a6 100644 --- a/doc/lightning-pay.7 +++ b/doc/lightning-pay.7 @@ -2,12 +2,12 @@ .\" Title: lightning-pay .\" Author: [see the "AUTHOR" section] .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 03/16/2018 +.\" Date: 03/21/2018 .\" Manual: \ \& .\" Source: \ \& .\" Language: English .\" -.TH "LIGHTNING\-PAY" "7" "03/16/2018" "\ \&" "\ \&" +.TH "LIGHTNING\-PAY" "7" "03/21/2018" "\ \&" "\ \&" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- @@ -31,12 +31,12 @@ lightning-pay \- Protocol for sending a payment to a BOLT11 invoice .SH "SYNOPSIS" .sp -\fBpay\fR \fIbolt11\fR [\fImsatoshi\fR] [\fIdescription\fR] [\fIriskfactor\fR] [\fImaxfeepercent\fR] [\fIretry_for\fR] +\fBpay\fR \fIbolt11\fR [\fImsatoshi\fR] [\fIdescription\fR] [\fIriskfactor\fR] [\fImaxfeepercent\fR] [\fIretry_for\fR] [\fImaxdelay\fR] .SH "DESCRIPTION" .sp The \fBpay\fR RPC command attempts to find a route to the given destination, and send the funds it asks for\&. If the \fIbolt11\fR does not contain an amount, \fImsatoshi\fR is required, otherwise if it is specified it must be \fInull\fR\&. If \fIbolt11\fR contains a description hash (\fIh\fR field) \fIdescription\fR is required, otherwise it is unused\&. The \fIriskfactor\fR is described in detail in lightning\-getroute(7), and defaults to 1\&.0\&. The \fImaxfeepercent\fR limits the money paid in fees, and defaults to 0\&.5\&. The \(oqmaxfeepercent\(cq is a percentage of the amount that is to be paid\&. .sp -The \fBpay\fR RPC command will randomize routes slightly, as long as the route achieves the targeted \fImaxfeepercent\fR\&. +The \fBpay\fR RPC command will randomize routes slightly, as long as the route achieves the targeted \fImaxfeepercent\fR and \fImaxdelay\fR\&. .sp The response will occur when the payment fails or succeeds\&. Once a payment has succeeded, calls to \fBpay\fR with the same \fIbolt11\fR will succeed immediately\&. .sp @@ -122,13 +122,19 @@ field of the error will be routing failure object\&. .sp -1 .IP \(bu 2.3 .\} -206\&. Route too expensive\&. The +206\&. Route too expensive\&. Either the fee or the needed total locktime for the route exceeds your +\fImaxfeepercent\fR +or +\fImaxdelay\fR +settings, respectively\&. The \fIdata\fR field of the error will indicate the actual \fIfee\fR as well as the \fIfeepercent\fR -percentage that the fee has of the destination payment amount\&. +percentage that the fee has of the destination payment amount\&. It will also indicate the actual +\fIdelay\fR +along the route\&. .RE .sp .RS 4 diff --git a/doc/lightning-pay.7.txt b/doc/lightning-pay.7.txt index f1be7dc25..4f5c0c58d 100644 --- a/doc/lightning-pay.7.txt +++ b/doc/lightning-pay.7.txt @@ -8,7 +8,7 @@ lightning-pay - Protocol for sending a payment to a BOLT11 invoice SYNOPSIS -------- -*pay* 'bolt11' ['msatoshi'] ['description'] ['riskfactor'] ['maxfeepercent'] ['retry_for'] +*pay* 'bolt11' ['msatoshi'] ['description'] ['riskfactor'] ['maxfeepercent'] ['retry_for'] ['maxdelay'] DESCRIPTION ----------- @@ -24,7 +24,7 @@ The `maxfeepercent' is a percentage of the amount that is to be paid. The *pay* RPC command will randomize routes slightly, as long as the -route achieves the targeted 'maxfeepercent'. +route achieves the targeted 'maxfeepercent' and 'maxdelay'. The response will occur when the payment fails or succeeds. Once a payment has succeeded, calls to *pay* with the same 'bolt11' will @@ -65,10 +65,14 @@ The following error codes may occur: * 203. Permanent failure at destination. The 'data' field of the error will be routing failure object. * 205. Unable to find a route. -* 206. Route too expensive. The 'data' field of the error will - indicate the actual 'fee' as well as the 'feepercent' - percentage that the fee has of the destination payment - amount. +* 206. Route too expensive. + Either the fee or the needed total locktime for the route + exceeds your 'maxfeepercent' or 'maxdelay' settings, + respectively. + The 'data' field of the error will indicate the actual 'fee' + as well as the 'feepercent' percentage that the fee has of the + destination payment amount. + It will also indicate the actual 'delay' along the route. * 207. Invoice expired. Payment took too long before expiration, or already expired at the time you initiated payment. The 'data' field of the error indicates 'now' (the current time) diff --git a/lightningd/payalgo.c b/lightningd/payalgo.c index eca43deb7..04603b6db 100644 --- a/lightningd/payalgo.c +++ b/lightningd/payalgo.c @@ -104,6 +104,7 @@ struct pay { u64 msatoshi; double riskfactor; double maxfeepercent; + u32 maxdelay; /* Number of getroute and sendpay tries */ unsigned int getroute_tries; @@ -407,7 +408,9 @@ static void json_pay_getroute_reply(struct subd *gossip UNUSED, u64 fee; double feepercent; bool fee_too_high; + bool delay_too_high; struct json_result *data; + char const *err; fromwire_gossip_getroute_reply(reply, reply, &route); @@ -434,30 +437,48 @@ static void json_pay_getroute_reply(struct subd *gossip UNUSED, * payments are limited to 4294967295 msatoshi. */ feepercent = ((double) fee) * 100.0 / ((double) pay->msatoshi); fee_too_high = (feepercent > pay->maxfeepercent); + delay_too_high = (route[0].delay > pay->maxdelay); /* compare fuzz to range */ - if (fee_too_high && pay->fuzz < 0.01) { + if ((fee_too_high || delay_too_high) && pay->fuzz < 0.01) { data = new_json_result(pay); json_object_start(data, NULL); + json_add_u64(data, "msatoshi", pay->msatoshi); 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_u64(data, "delay", (u64) route[0].delay); + json_add_num(data, "maxdelay", pay->maxdelay); json_add_num(data, "getroute_tries", pay->getroute_tries); json_add_num(data, "sendpay_tries", pay->sendpay_tries); + json_add_route(data, "route", + route, tal_count(route)); json_add_failures(data, "failures", &pay->pay_failures); json_object_end(data); - command_fail_detailed(pay->cmd, PAY_ROUTE_TOO_EXPENSIVE, - data, + err = ""; + if (fee_too_high) + err = tal_fmt(pay, "Fee %"PRIu64" is %f%% " "of payment %"PRIu64"; " - "max fee requested is %f%%", + "max fee requested is %f%%.", fee, feepercent, pay->msatoshi, pay->maxfeepercent); + if (fee_too_high && delay_too_high) + err = tal_fmt(pay, "%s ", err); + if (delay_too_high) + err = tal_fmt(pay, + "%s" + "Delay (locktime) is %"PRIu32" blocks; " + "max delay requested is %u.", + err, route[0].delay, pay->maxdelay); + + + command_fail_detailed(pay->cmd, PAY_ROUTE_TOO_EXPENSIVE, + data, "%s", err); return; } - if (fee_too_high) { + if (fee_too_high || delay_too_high) { /* Retry with lower fuzz */ pay->fuzz -= 0.15; if (pay->fuzz <= 0.0) @@ -575,6 +596,7 @@ static void json_pay(struct command *cmd, { jsmntok_t *bolt11tok, *msatoshitok, *desctok, *riskfactortok, *maxfeetok; jsmntok_t *retryfortok; + jsmntok_t *maxdelaytok; double riskfactor = 1.0; double maxfeepercent = 0.5; u64 msatoshi; @@ -582,6 +604,7 @@ static void json_pay(struct command *cmd, struct bolt11 *b11; char *fail, *b11str, *desc; unsigned int retryfor = 60; + unsigned int maxdelay = 500; if (!json_get_params(cmd, buffer, params, "bolt11", &bolt11tok, @@ -590,6 +613,7 @@ static void json_pay(struct command *cmd, "?riskfactor", &riskfactortok, "?maxfeepercent", &maxfeetok, "?retry_for", &retryfortok, + "?maxdelay", &maxdelaytok, NULL)) { return; } @@ -672,14 +696,32 @@ static void json_pay(struct command *cmd, } pay->maxfeepercent = maxfeepercent; + if (maxdelaytok + && !json_tok_number(buffer, maxdelaytok, &maxdelay)) { + command_fail(cmd, "'%.*s' is not a valid double", + maxdelaytok->end - maxdelaytok->start, + buffer + maxdelaytok->start); + return; + } + if (maxdelay < pay->min_final_cltv_expiry) { + command_fail(cmd, + "maxdelay (%u) must be greater than " + "min_final_cltv_expiry (%"PRIu32") of " + "invoice", + maxdelay, pay->min_final_cltv_expiry); + return; + } + pay->maxdelay = maxdelay; + 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 + * `maxfeepercent` or `maxdelay` we reduce the fuzz. + * Starting with high + * fuzz means, if the user allows high fee/locktime, we can take * advantage of that to increase randomization and * improve privacy somewhat. */ pay->fuzz = 0.75;