Browse Source

paymod: Add user-provided label back into the paystatus result

keysend
Christian Decker 5 years ago
parent
commit
2331cd62e1
  1. 180
      plugins/libplugin-pay.c
  2. 12
      plugins/libplugin-pay.h
  3. 17
      plugins/pay.c
  4. 2
      tests/test_pay.py

180
plugins/libplugin-pay.c

@ -2,10 +2,9 @@
#include <ccan/array_size/array_size.h>
#include <ccan/tal/str/str.h>
#include <common/json_stream.h>
#include <common/pseudorand.h>
#include <common/type_to_string.h>
#include <plugins/libplugin-pay.h>
#include <stdio.h>
#define DEFAULT_FINAL_CLTV_DELTA 9
@ -22,6 +21,7 @@ struct payment *payment_new(tal_t *ctx, struct command *cmd,
p->result = NULL;
p->why = NULL;
p->getroute = tal(p, struct getroute_request);
p->label = NULL;
/* Copy over the relevant pieces of information. */
if (parent != NULL) {
@ -1178,8 +1178,6 @@ void payment_continue(struct payment *p)
void payment_fail(struct payment *p)
{
va_list ap;
p->end_time = time_now();
p->step = PAYMENT_STEP_FAILED;
payment_continue(p);
@ -1607,3 +1605,177 @@ static void exemptfee_cb(struct exemptfee_data *d, struct payment *p)
REGISTER_PAYMENT_MODIFIER(exemptfee, struct exemptfee_data *,
exemptfee_data_init, exemptfee_cb);
/* 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 shadow_route_data *shadow_route_init(struct payment *p)
{
if (p->parent != NULL)
return payment_mod_shadowroute_get_data(p->parent);
else
return tal(p, struct shadow_route_data);
}
/* Mutual recursion */
static struct command_result *shadow_route_listchannels(struct command *cmd,
const char *buf,
const jsmntok_t *result,
struct payment *p);
static struct command_result *shadow_route_extend(struct shadow_route_data *d,
struct payment *p)
{
struct out_req *req;
req = jsonrpc_request_start(p->plugin, NULL, "listchannels",
shadow_route_listchannels,
payment_rpc_failure, p);
json_add_string(req->js, "source",
type_to_string(req, struct node_id, &d->destination));
return send_outreq(p->plugin, req);
}
static struct command_result *shadow_route_listchannels(struct command *cmd,
const char *buf,
const jsmntok_t *result,
struct payment *p)
{
/* Use reservoir sampling across the capable channels. */
struct shadow_route_data *d = payment_mod_shadowroute_get_data(p);
struct payment_constraints *cons = &d->constraints;
struct route_info *best = NULL;
size_t i;
u64 sample = 0;
struct amount_msat best_fee;
const jsmntok_t *sattok, *delaytok, *basefeetok, *propfeetok, *desttok,
*channelstok, *chan;
channelstok = json_get_member(buf, result, "channels");
json_for_each_arr(i, chan, channelstok) {
u64 v = pseudorand(UINT64_MAX);
struct route_info curr;
struct amount_sat capacity;
struct amount_msat fee;
sattok = json_get_member(buf, chan, "satoshis");
delaytok = json_get_member(buf, chan, "delay");
basefeetok = json_get_member(buf, chan, "base_fee_millisatoshi");
propfeetok = json_get_member(buf, chan, "fee_per_millionth");
desttok = json_get_member(buf, chan, "destination");
if (sattok == NULL || delaytok == NULL ||
delaytok->type != JSMN_PRIMITIVE || basefeetok == NULL ||
basefeetok->type != JSMN_PRIMITIVE || propfeetok == NULL ||
propfeetok->type != JSMN_PRIMITIVE || desttok == NULL)
continue;
json_to_u16(buf, delaytok, &curr.cltv_expiry_delta);
json_to_number(buf, basefeetok, &curr.fee_base_msat);
json_to_number(buf, propfeetok,
&curr.fee_proportional_millionths);
json_to_sat(buf, sattok, &capacity);
json_to_node_id(buf, desttok, &curr.pubkey);
if (!best || v > sample) {
/* If the capacity is insufficient to pass the amount
* it's not a plausible extension. */
if (amount_msat_greater_sat(p->amount, capacity))
continue;
if (curr.cltv_expiry_delta > cons->cltv_budget)
continue;
if (!amount_msat_fee(
&fee, p->amount, curr.fee_base_msat,
curr.fee_proportional_millionths)) {
/* Fee computation failed... */
continue;
}
if (amount_msat_greater_eq(fee, cons->fee_budget))
continue;
best = tal_dup(tmpctx, struct route_info, &curr);
best_fee = fee;
sample = v;
}
}
if (best != NULL) {
bool ok;
/* Ok, we found an extension, let's add it. */
d->destination = best->pubkey;
/* Apply deltas to the constraints in the shadow route so we
* don't overshoot our 1/4th target. */
if (!payment_constraints_update(&d->constraints, best_fee,
best->cltv_expiry_delta)) {
best = NULL;
goto next;
}
/* Now do the same to the payment constraints so other
* modifiers don't do it either. */
ok = payment_constraints_update(&p->constraints, best_fee,
best->cltv_expiry_delta);
/* And now the thing that caused all of this: adjust the call
* to getroute. */
ok &= amount_msat_add(&p->getroute->amount, p->getroute->amount,
best_fee);
p->getroute->cltv += best->cltv_expiry_delta;
assert(ok);
}
next:
/* Now it's time to decide whether we want to extend or continue. */
if (best == NULL || pseudorand(2) == 0) {
payment_continue(p);
return command_still_pending(cmd);
} else {
return shadow_route_extend(d, p);
}
}
static void shadow_route_cb(struct shadow_route_data *d,
struct payment *p)
{
#if DEVELOPER
if (!d->use_shadow)
return payment_continue(p);
#endif
if (p->step != PAYMENT_STEP_INITIALIZED)
return payment_continue(p);
d->destination = *p->destination;
/* Allow shadowroutes to consume up to 1/4th of our budget. */
d->constraints.cltv_budget = p->constraints.cltv_budget / 4;
d->constraints.fee_budget = p->constraints.fee_budget;
d->constraints.fee_budget.millisatoshis /= 4; /* Raw: msat division. */
if (pseudorand(2) == 0) {
return payment_continue(p);
} else {
shadow_route_extend(d, p);
}
}
REGISTER_PAYMENT_MODIFIER(shadowroute, struct shadow_route_data *,
shadow_route_init, shadow_route_cb);

12
plugins/libplugin-pay.h

@ -256,6 +256,8 @@ struct payment {
/* Textual explanation of why this payment was attempted. */
const char *why;
const char *label;
};
struct payment_modifier {
@ -311,10 +313,20 @@ struct exemptfee_data {
* payment through. */
struct amount_msat amount;
};
struct shadow_route_data {
#if DEVELOPER
bool use_shadow;
#endif
struct payment_constraints constraints;
struct node_id destination;
struct route_hop *route;
};
/* List of globally available payment modifiers. */
REGISTER_PAYMENT_MODIFIER_HEADER(retry, struct retry_mod_data);
REGISTER_PAYMENT_MODIFIER_HEADER(routehints, struct routehints_data);
REGISTER_PAYMENT_MODIFIER_HEADER(exemptfee, struct exemptfee_data);
REGISTER_PAYMENT_MODIFIER_HEADER(shadowroute, struct shadow_route_data);
/* For the root payment we can seed the channel_hints with the result from
* `listpeers`, hence avoid channels that we know have insufficient capacity

17
plugins/pay.c

@ -1586,6 +1586,9 @@ static struct command_result *json_paystatus(struct command *cmd,
continue;
json_object_start(ret, NULL);
if (p->label != NULL)
json_add_string(ret, "label", p->label);
if (p->bolt11)
json_add_string(ret, "bolt11", p->bolt11);
json_add_amount_msat_only(ret, "amount_msat", p->amount);
@ -1834,6 +1837,7 @@ static void init(struct plugin *p,
}
struct payment_modifier *paymod_mods[] = {
&shadowroute_pay_mod,
&exemptfee_pay_mod,
&routehints_pay_mod,
&local_channel_hints_pay_mod,
@ -1855,6 +1859,10 @@ static struct command_result *json_paymod(struct command *cmd,
u64 *maxfee_pct_millionths;
u32 *maxdelay;
struct amount_msat *exemptfee;
const char *label;
#if DEVELOPER
bool *use_shadow;
#endif
p = payment_new(NULL, cmd, NULL /* No parent */, paymod_mods);
@ -1862,11 +1870,15 @@ static struct command_result *json_paymod(struct command *cmd,
* would add them to the `param()` call below, and have them be
* initialized directly that way. */
if (!param(cmd, buf, params, p_req("bolt11", param_string, &b11str),
p_opt("label", param_string, &label),
p_opt_def("exemptfee", param_msat, &exemptfee, AMOUNT_MSAT(5000)),
p_opt_def("maxdelay", param_number, &maxdelay,
maxdelay_default),
p_opt_def("maxfeepercent", param_millionths,
&maxfee_pct_millionths, 500000),
#if DEVELOPER
p_opt_def("use_shadow", param_bool, &use_shadow, true),
#endif
NULL))
return command_param_failed();
@ -1920,7 +1932,10 @@ static struct command_result *json_paymod(struct command *cmd,
p->constraints.cltv_budget = *maxdelay;
payment_mod_exemptfee_get_data(p)->amount = *exemptfee;
#if DEVELOPER
payment_mod_shadowroute_get_data(p)->use_shadow = *use_shadow;
#endif
p->label = tal_steal(p, label);
payment_start(p);
list_add_tail(&payments, &p->list);

2
tests/test_pay.py

@ -3077,7 +3077,7 @@ def test_pay_modifiers(node_factory):
# Make sure that the dummy param is in the help (and therefore assigned to
# the modifier data).
hlp = l1.rpc.help("paymod")['help'][0]
assert(hlp['command'] == 'paymod bolt11 [exemptfee] [maxdelay] [maxfeepercent]')
assert(hlp['command'] == 'paymod bolt11 [label] [exemptfee] [maxdelay] [maxfeepercent] [use_shadow]')
inv = l2.rpc.invoice(123, 'lbl', 'desc')['bolt11']
r = l1.rpc.paymod(inv)

Loading…
Cancel
Save