mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-01-18 13:25:43 +01:00
paymod: Exclude most expensive/slowest chan if limits are exceeded
This commit is contained in:
parent
4648d2867f
commit
935578e567
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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…
Reference in New Issue
Block a user