Browse Source

paymod: Exclude most expensive/slowest chan if limits are exceeded

keysend
Christian Decker 4 years ago
parent
commit
935578e567
  1. 78
      plugins/libplugin-pay.c
  2. 7
      plugins/libplugin-pay.h
  3. 6
      plugins/pay.c
  4. 38
      tests/test_pay.py

78
plugins/libplugin-pay.c

@ -216,6 +216,48 @@ tal_route_from_json(const tal_t *ctx, const char *buffer, const jsmntok_t *toks)
return hops;
}
static void payment_exclude_most_expensive(struct payment *p)
{
struct payment *root = payment_root(p);
struct route_hop *e = &p->route[0];
struct amount_msat fee, worst = AMOUNT_MSAT(0);
struct channel_hint hint;
for (size_t i = 0; i < tal_count(p->route)-1; i++) {
if (!amount_msat_sub(&fee, p->route[i].amount, p->route[i+1].amount))
plugin_err(p->plugin, "Negative fee in a route.");
if (amount_msat_greater_eq(fee, worst)) {
e = &p->route[i];
worst = fee;
}
}
hint.scid.scid = e->channel_id;
hint.scid.dir = e->direction;
hint.enabled = false;
tal_arr_expand(&root->channel_hints, hint);
}
static void payment_exclude_longest_delay(struct payment *p)
{
struct payment *root = payment_root(p);
struct route_hop *e = &p->route[0];
u32 delay, worst = 0;
struct channel_hint hint;
for (size_t i = 0; i < tal_count(p->route)-1; i++) {
delay = p->route[i].delay - p->route[i+1].delay;
if (delay >= worst) {
e = &p->route[i];
worst = delay;
}
}
hint.scid.scid = e->channel_id;
hint.scid.dir = e->direction;
hint.enabled = false;
tal_arr_expand(&root->channel_hints, hint);
}
static struct command_result *payment_getroute_result(struct command *cmd,
const char *buffer,
const jsmntok_t *toks,
@ -246,6 +288,7 @@ static struct command_result *payment_getroute_result(struct command *cmd,
"Fee exceeds our fee budget: %s > %s, discarding route",
type_to_string(tmpctx, struct amount_msat, &fee),
type_to_string(tmpctx, struct amount_msat, &p->fee_budget));
payment_exclude_most_expensive(p);
payment_fail(p);
return command_still_pending(cmd);
}
@ -254,6 +297,7 @@ static struct command_result *payment_getroute_result(struct command *cmd,
plugin_log(p->plugin, LOG_INFORM,
"CLTV delay exceeds our CLTV budget: %d > %d",
p->route[0].delay, p->cltv_budget);
payment_exclude_longest_delay(p);
payment_fail(p);
return command_still_pending(cmd);
}
@ -1493,3 +1537,37 @@ static struct routehints_data *routehint_data_init(struct payment *p)
REGISTER_PAYMENT_MODIFIER(routehints, struct routehints_data *,
routehint_data_init, routehint_step_cb);
/* For tiny payments the fees incurred due to the fixed base_fee may dominate
* the overall cost of the payment. Since these payments are often used as a
* way to signal, rather than actually transfer the amount, we add an
* exemption that allows tiny payments to exceed the fee allowance. This is
* implemented by setting a larger allowance than we would normally do if the
* payment is below the threshold. */
static struct exemptfee_data *exemptfee_data_init(struct payment *p)
{
struct exemptfee_data *d = tal(p, struct exemptfee_data);
d->amount = AMOUNT_MSAT(5000);
return d;
}
static void exemptfee_cb(struct exemptfee_data *d, struct payment *p)
{
if (p->step != PAYMENT_STEP_INITIALIZED)
return payment_continue(p);
if (amount_msat_greater_eq(d->amount, p->amount)) {
p->fee_budget = d->amount;
plugin_log(
p->plugin, LOG_INFORM,
"Payment amount is below exemption threshold, "
"allowing a maximum fee of %s",
type_to_string(tmpctx, struct amount_msat, &p->fee_budget));
}
return payment_continue(p);
}
REGISTER_PAYMENT_MODIFIER(exemptfee, struct exemptfee_data *,
exemptfee_data_init, exemptfee_cb);

7
plugins/libplugin-pay.h

@ -293,9 +293,16 @@ struct routehints_data {
u32 final_cltv;
};
struct exemptfee_data {
/* Amounts below this amount will get their fee limit raised to
* exemptfee, i.e., we're willing to pay twice exemptfee to get this
* payment through. */
struct amount_msat amount;
};
/* 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);
/* For the root payment we can seed the channel_hints with the result from
* `listpeers`, hence avoid channels that we know have insufficient capacity

6
plugins/pay.c

@ -1834,6 +1834,7 @@ static void init(struct plugin *p,
}
struct payment_modifier *paymod_mods[] = {
&exemptfee_pay_mod,
&routehints_pay_mod,
&local_channel_hints_pay_mod,
&retry_pay_mod,
@ -1853,6 +1854,7 @@ static struct command_result *json_paymod(struct command *cmd,
char *fail;
u64 *maxfee_pct_millionths;
u32 *maxdelay;
struct amount_msat *exemptfee;
p = payment_new(NULL, cmd, NULL /* No parent */, paymod_mods);
@ -1860,6 +1862,7 @@ 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_def("exemptfee", param_msat, &exemptfee, AMOUNT_MSAT(5000)),
p_opt_def("maxdelay", param_number, &maxdelay,
maxdelay_default),
p_opt_def("maxfeepercent", param_millionths,
@ -1913,6 +1916,9 @@ static struct command_result *json_paymod(struct command *cmd,
cmd, JSONRPC2_INVALID_PARAMS,
"Overflow when computing fee budget, fee rate too high.");
}
payment_mod_exemptfee_get_data(p)->amount = *exemptfee;
payment_start(p);
list_add_tail(&payments, &p->list);

38
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 [maxdelay] [maxfeepercent]')
assert(hlp['command'] == 'paymod bolt11 [exemptfee] [maxdelay] [maxfeepercent]')
inv = l2.rpc.invoice(123, 'lbl', 'desc')['bolt11']
r = l1.rpc.paymod(inv)
@ -3085,7 +3085,39 @@ def test_pay_modifiers(node_factory):
assert(sha256(unhexlify(r['payment_preimage'])).hexdigest() == r['payment_hash'])
def test_pay_exemptfee(node_factory):
@unittest.skipIf(not DEVELOPER, "Requires use_shadow")
def test_pay_exemptfee(node_factory, compat):
"""Tiny payment, huge fee
l1 -> l2 -> l3
Create a tiny invoice for 1 msat, it'll be dominated by the base_fee on
the l2->l3 channel. So it'll get rejected on the first attempt if we set
the exemptfee way too low. The default fee exemption threshold is
5000msat, so 5001msat is not exempted by default and a 5001msat fee on
l2->l3 should trigger this.
"""
l1, l2, l3 = node_factory.line_graph(3)
l1, l2, l3 = node_factory.line_graph(
3,
opts=[{}, {'fee-base': 5001, 'fee-per-satoshi': 0}, {}],
wait_for_announce=True
)
err = r'Route wanted fee of 5001msat' if compat('090') else r'Ran out of routes to try'
with pytest.raises(RpcError, match=err):
l1.rpc.dev_pay(l3.rpc.invoice(1, "lbl1", "desc")['bolt11'], use_shadow=False)
# If we tell our node that 5001msat is ok this should work
l1.rpc.dev_pay(l3.rpc.invoice(1, "lbl2", "desc")['bolt11'], use_shadow=False, exemptfee=5001)
# Given the above network this is the smallest amount that passes without
# the fee-exemption (notice that we let it through on equality).
threshold = int(5001 / 0.05)
# This should be just below the fee-exemption and is the first value that is allowed through
with pytest.raises(RpcError, match=err):
l1.rpc.dev_pay(l3.rpc.invoice(threshold - 1, "lbl3", "desc")['bolt11'], use_shadow=False)
l1.rpc.pay(inv, exemptfee=5001)

Loading…
Cancel
Save