mirror of
https://github.com/aljazceru/lightning.git
synced 2025-12-23 00:54:20 +01:00
paymod: Exclude most expensive/slowest chan if limits are exceeded
This commit is contained in:
@@ -216,6 +216,48 @@ tal_route_from_json(const tal_t *ctx, const char *buffer, const jsmntok_t *toks)
|
|||||||
return hops;
|
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,
|
static struct command_result *payment_getroute_result(struct command *cmd,
|
||||||
const char *buffer,
|
const char *buffer,
|
||||||
const jsmntok_t *toks,
|
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",
|
"Fee exceeds our fee budget: %s > %s, discarding route",
|
||||||
type_to_string(tmpctx, struct amount_msat, &fee),
|
type_to_string(tmpctx, struct amount_msat, &fee),
|
||||||
type_to_string(tmpctx, struct amount_msat, &p->fee_budget));
|
type_to_string(tmpctx, struct amount_msat, &p->fee_budget));
|
||||||
|
payment_exclude_most_expensive(p);
|
||||||
payment_fail(p);
|
payment_fail(p);
|
||||||
return command_still_pending(cmd);
|
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,
|
plugin_log(p->plugin, LOG_INFORM,
|
||||||
"CLTV delay exceeds our CLTV budget: %d > %d",
|
"CLTV delay exceeds our CLTV budget: %d > %d",
|
||||||
p->route[0].delay, p->cltv_budget);
|
p->route[0].delay, p->cltv_budget);
|
||||||
|
payment_exclude_longest_delay(p);
|
||||||
payment_fail(p);
|
payment_fail(p);
|
||||||
return command_still_pending(cmd);
|
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 *,
|
REGISTER_PAYMENT_MODIFIER(routehints, struct routehints_data *,
|
||||||
routehint_data_init, routehint_step_cb);
|
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;
|
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. */
|
/* List of globally available payment modifiers. */
|
||||||
REGISTER_PAYMENT_MODIFIER_HEADER(retry, struct retry_mod_data);
|
REGISTER_PAYMENT_MODIFIER_HEADER(retry, struct retry_mod_data);
|
||||||
REGISTER_PAYMENT_MODIFIER_HEADER(routehints, struct routehints_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
|
/* For the root payment we can seed the channel_hints with the result from
|
||||||
* `listpeers`, hence avoid channels that we know have insufficient capacity
|
* `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[] = {
|
struct payment_modifier *paymod_mods[] = {
|
||||||
|
&exemptfee_pay_mod,
|
||||||
&routehints_pay_mod,
|
&routehints_pay_mod,
|
||||||
&local_channel_hints_pay_mod,
|
&local_channel_hints_pay_mod,
|
||||||
&retry_pay_mod,
|
&retry_pay_mod,
|
||||||
@@ -1853,6 +1854,7 @@ static struct command_result *json_paymod(struct command *cmd,
|
|||||||
char *fail;
|
char *fail;
|
||||||
u64 *maxfee_pct_millionths;
|
u64 *maxfee_pct_millionths;
|
||||||
u32 *maxdelay;
|
u32 *maxdelay;
|
||||||
|
struct amount_msat *exemptfee;
|
||||||
|
|
||||||
p = payment_new(NULL, cmd, NULL /* No parent */, paymod_mods);
|
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
|
* would add them to the `param()` call below, and have them be
|
||||||
* initialized directly that way. */
|
* initialized directly that way. */
|
||||||
if (!param(cmd, buf, params, p_req("bolt11", param_string, &b11str),
|
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,
|
p_opt_def("maxdelay", param_number, &maxdelay,
|
||||||
maxdelay_default),
|
maxdelay_default),
|
||||||
p_opt_def("maxfeepercent", param_millionths,
|
p_opt_def("maxfeepercent", param_millionths,
|
||||||
@@ -1913,6 +1916,9 @@ static struct command_result *json_paymod(struct command *cmd,
|
|||||||
cmd, JSONRPC2_INVALID_PARAMS,
|
cmd, JSONRPC2_INVALID_PARAMS,
|
||||||
"Overflow when computing fee budget, fee rate too high.");
|
"Overflow when computing fee budget, fee rate too high.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
payment_mod_exemptfee_get_data(p)->amount = *exemptfee;
|
||||||
|
|
||||||
payment_start(p);
|
payment_start(p);
|
||||||
list_add_tail(&payments, &p->list);
|
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
|
# Make sure that the dummy param is in the help (and therefore assigned to
|
||||||
# the modifier data).
|
# the modifier data).
|
||||||
hlp = l1.rpc.help("paymod")['help'][0]
|
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']
|
inv = l2.rpc.invoice(123, 'lbl', 'desc')['bolt11']
|
||||||
r = l1.rpc.paymod(inv)
|
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'])
|
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
|
"""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)
|
||||||
|
|||||||
Reference in New Issue
Block a user