Browse Source

plugins/pay: implement maxfeepercent, maxdelay and exemptfee.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
plugin-timeout-inc
Rusty Russell 6 years ago
committed by Christian Decker
parent
commit
a8f2f28c72
  1. 44
      plugins/pay.c
  2. 25
      tests/test_pay.py

44
plugins/pay.c

@ -19,6 +19,11 @@ struct pay_command {
u64 msatoshi;
double riskfactor;
/* Limits on what routes we'll accept. */
double maxfeepercent;
unsigned int maxdelay;
u64 exemptfee;
/* Payment hash, as text. */
const char *payment_hash;
@ -121,10 +126,40 @@ static struct command_result *getroute_done(struct command *cmd,
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);
}
attempt.route = json_strdup(pc->attempts, buf, result);
tal_arr_expand(&pc->attempts, attempt);
@ -230,11 +265,9 @@ static struct command_result *handle_pay(struct command *cmd,
double *riskfactor;
unsigned int *retryfor;
struct pay_command *pc = tal(cmd, struct pay_command);
/* FIXME! */
double *maxfeepercent;
unsigned int *maxdelay;
unsigned int *exemptfee;
u64 *exemptfee;
setup_locale();
@ -248,7 +281,7 @@ static struct command_result *handle_pay(struct command *cmd,
p_opt_def("maxdelay", param_number, &maxdelay,
/* FIXME! */
14 * 24 * 6),
p_opt_def("exemptfee", param_number, &exemptfee, 5000),
p_opt_def("exemptfee", param_u64, &exemptfee, 5000),
NULL))
return NULL;
@ -276,6 +309,9 @@ static struct command_result *handle_pay(struct command *cmd,
pc->msatoshi = *msatoshi;
}
pc->maxfeepercent = *maxfeepercent;
pc->maxdelay = *maxdelay;
pc->exemptfee = *exemptfee;
pc->riskfactor = *riskfactor;
pc->dest = type_to_string(cmd, struct pubkey, &b11->receiver_id);
pc->payment_hash = type_to_string(pc, struct sha256,

25
tests/test_pay.py

@ -59,6 +59,31 @@ def test_pay(node_factory):
assert len(payments) == 1 and payments[0]['payment_preimage'] == preimage
def test_pay_limits(node_factory):
"""Test that we enforce fee max percentage and max delay"""
l1, l2, l3 = node_factory.line_graph(3, wait_for_announce=True)
# FIXME: pylightning should define these!
PAY_ROUTE_TOO_EXPENSIVE = 206
inv = l3.rpc.invoice("any", "any", 'description')
# Fee too high.
with pytest.raises(RpcError, match=r'Route wanted fee of .* msatoshis') as err:
l1.rpc.call('pay2', {'bolt11': inv['bolt11'], 'msatoshi': 100000, 'maxfeepercent': 0.0001, 'exemptfee': 0})
assert err.value.error['code'] == PAY_ROUTE_TOO_EXPENSIVE
# Delay too high.
with pytest.raises(RpcError, match=r'Route wanted delay of .* blocks') as err:
l1.rpc.call('pay2', {'bolt11': inv['bolt11'], 'msatoshi': 100000, 'maxdelay': 0})
assert err.value.error['code'] == PAY_ROUTE_TOO_EXPENSIVE
# This works, because fee is less than exemptfee.
l1.rpc.call('pay2', {'bolt11': inv['bolt11'], 'msatoshi': 100000, 'maxfeepercent': 0.0001, 'exemptfee': 2000})
def test_pay0(node_factory):
"""Test paying 0 amount
"""

Loading…
Cancel
Save