mirror of
https://github.com/ElementsProject/lightning.git
synced 2024-11-19 01:43:36 +01:00
pay: add absolute "maxfee" parameter.
This is what LND does, and it's better for upper layers than trying to twist our maxfeepercent / exemptfee heuristics to suit. (I don't remember who complained about this, sorry!) I'm doing this now because I want to add YA parameter next! Signed-off-by: Rusty Russell <rusty@rustcorp.com.au> Changelog-Added: JSON-RPC: `pay` has new parameter `maxfee` for setting absolute fee (instead of using `maxfeepercent` and/or `exemptfee`)
This commit is contained in:
parent
713cd0540d
commit
6c54e0e7e7
@ -997,7 +997,8 @@ class LightningRpc(UnixDomainSocketRpc):
|
||||
|
||||
def pay(self, bolt11, msatoshi=None, label=None, riskfactor=None,
|
||||
maxfeepercent=None, retry_for=None,
|
||||
maxdelay=None, exemptfee=None, localofferid=None, exclude=None):
|
||||
maxdelay=None, exemptfee=None, localofferid=None, exclude=None,
|
||||
maxfee=None):
|
||||
"""
|
||||
Send payment specified by {bolt11} with {msatoshi}
|
||||
(ignored if {bolt11} has an amount), optional {label}
|
||||
@ -1014,6 +1015,7 @@ class LightningRpc(UnixDomainSocketRpc):
|
||||
"exemptfee": exemptfee,
|
||||
"localofferid": localofferid,
|
||||
"exclude": exclude,
|
||||
"maxfee": maxfee,
|
||||
}
|
||||
return self.call("pay", payload)
|
||||
|
||||
|
@ -5,8 +5,8 @@ SYNOPSIS
|
||||
--------
|
||||
|
||||
**pay** *bolt11* [*msatoshi*] [*label*] [*riskfactor*]
|
||||
[*maxfeepercent*] [*retry\_for*] [*maxdelay*] [*exemptfee*]
|
||||
[*localofferid*] [*exclude*]
|
||||
[*maxfeepercent*] [*retry_for*] [*maxdelay*] [*exemptfee*]
|
||||
[*localofferid*] [*exclude*] [*maxfee*]
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
@ -37,6 +37,12 @@ leveraged by forwarding nodes. Setting `exemptfee` allows the
|
||||
that we only make a single payment for an offer, and that the offer is
|
||||
marked `used` once paid.
|
||||
|
||||
*maxfee* overrides both *maxfeepercent* and *exemptfee* defaults (and
|
||||
if you specify *maxfee* you cannot specify either of those), and
|
||||
creates an absolute limit on what fee we will pay. This allows you to
|
||||
implement your own heuristics rather than the primitive ones used
|
||||
here.
|
||||
|
||||
The response will occur when the payment fails or succeeds. Once a
|
||||
payment has succeeded, calls to **pay** with the same *bolt11* will
|
||||
succeed immediately.
|
||||
|
@ -45,6 +45,9 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"maxfee": {
|
||||
"type": "msat"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -925,7 +925,7 @@ static struct command_result *json_pay(struct command *cmd,
|
||||
char *b11_fail, *b12_fail;
|
||||
u64 *maxfee_pct_millionths;
|
||||
u32 *maxdelay;
|
||||
struct amount_msat *exemptfee, *msat;
|
||||
struct amount_msat *exemptfee, *msat, *maxfee;
|
||||
const char *label;
|
||||
unsigned int *retryfor;
|
||||
u64 *riskfactor_millionths;
|
||||
@ -950,14 +950,15 @@ static struct command_result *json_pay(struct command *cmd,
|
||||
p_opt("label", param_string, &label),
|
||||
p_opt_def("riskfactor", param_millionths,
|
||||
&riskfactor_millionths, 10000000),
|
||||
p_opt_def("maxfeepercent", param_millionths,
|
||||
&maxfee_pct_millionths, 500000),
|
||||
p_opt("maxfeepercent", param_millionths,
|
||||
&maxfee_pct_millionths),
|
||||
p_opt_def("retry_for", param_number, &retryfor, 60),
|
||||
p_opt_def("maxdelay", param_number, &maxdelay,
|
||||
maxdelay_default),
|
||||
p_opt_def("exemptfee", param_msat, &exemptfee, AMOUNT_MSAT(5000)),
|
||||
p_opt("exemptfee", param_msat, &exemptfee),
|
||||
p_opt("localofferid", param_sha256, &local_offer_id),
|
||||
p_opt("exclude", param_route_exclusion_array, &exclusions),
|
||||
p_opt("maxfee", param_msat, &maxfee),
|
||||
#if DEVELOPER
|
||||
p_opt_def("use_shadow", param_bool, &use_shadow, true),
|
||||
#endif
|
||||
@ -1103,17 +1104,35 @@ static struct command_result *json_pay(struct command *cmd,
|
||||
p->getroute->riskfactorppm = *riskfactor_millionths;
|
||||
tal_free(riskfactor_millionths);
|
||||
|
||||
/* We free unneeded params as we use them, to keep memleak happy. */
|
||||
if (!amount_msat_fee(&p->constraints.fee_budget, p->amount, 0,
|
||||
*maxfee_pct_millionths / 100)) {
|
||||
return command_fail(
|
||||
cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"Overflow when computing fee budget, fee rate too high.");
|
||||
}
|
||||
tal_free(maxfee_pct_millionths);
|
||||
if (maxfee) {
|
||||
if (maxfee_pct_millionths || exemptfee) {
|
||||
return command_fail(
|
||||
cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"If you specify maxfee, cannot specify maxfeepercent or exemptfee.");
|
||||
}
|
||||
p->constraints.fee_budget = *maxfee;
|
||||
payment_mod_exemptfee_get_data(p)->amount = AMOUNT_MSAT(0);
|
||||
} else {
|
||||
u64 maxppm;
|
||||
|
||||
if (maxfee_pct_millionths)
|
||||
maxppm = *maxfee_pct_millionths / 100;
|
||||
else
|
||||
maxppm = 500000 / 100;
|
||||
if (!amount_msat_fee(&p->constraints.fee_budget, p->amount, 0,
|
||||
maxppm)) {
|
||||
return command_fail(
|
||||
cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"Overflow when computing fee budget, fee rate too high.");
|
||||
}
|
||||
payment_mod_exemptfee_get_data(p)->amount
|
||||
= exemptfee ? *exemptfee : AMOUNT_MSAT(5000);
|
||||
|
||||
/* We free unneeded params now to keep memleak happy. */
|
||||
tal_free(maxfee_pct_millionths);
|
||||
tal_free(exemptfee);
|
||||
}
|
||||
|
||||
payment_mod_exemptfee_get_data(p)->amount = *exemptfee;
|
||||
tal_free(exemptfee);
|
||||
shadow_route = payment_mod_shadowroute_get_data(p);
|
||||
payment_mod_presplit_get_data(p)->disable = disablempp;
|
||||
payment_mod_adaptive_splitter_get_data(p)->disable = disablempp;
|
||||
|
@ -128,10 +128,15 @@ def test_pay_limits(node_factory):
|
||||
assert(len(status) == 2)
|
||||
assert(status[0]['failure']['code'] == 205)
|
||||
|
||||
# This fails!
|
||||
err = r'Fee exceeds our fee budget: 2msat > 1msat, discarding route'
|
||||
with pytest.raises(RpcError, match=err) as err:
|
||||
l1.rpc.pay(bolt11=inv['bolt11'], msatoshi=100000, maxfee=1)
|
||||
|
||||
# This works, because fee is less than exemptfee.
|
||||
l1.dev_pay(inv['bolt11'], msatoshi=100000, maxfeepercent=0.0001,
|
||||
exemptfee=2000, use_shadow=False)
|
||||
status = l1.rpc.call('paystatus', {'bolt11': inv['bolt11']})['pay'][2]['attempts']
|
||||
status = l1.rpc.call('paystatus', {'bolt11': inv['bolt11']})['pay'][3]['attempts']
|
||||
assert len(status) == 1
|
||||
assert status[0]['strategy'] == "Initial attempt"
|
||||
|
||||
|
@ -397,7 +397,8 @@ def test_pay_plugin(node_factory):
|
||||
|
||||
# Make sure usage messages are present.
|
||||
msg = 'pay bolt11 [msatoshi] [label] [riskfactor] [maxfeepercent] '\
|
||||
'[retry_for] [maxdelay] [exemptfee] [localofferid] [exclude]'
|
||||
'[retry_for] [maxdelay] [exemptfee] [localofferid] [exclude] '\
|
||||
'[maxfee]'
|
||||
if DEVELOPER:
|
||||
msg += ' [use_shadow]'
|
||||
assert only_one(l1.rpc.help('pay')['help'])['command'] == msg
|
||||
|
Loading…
Reference in New Issue
Block a user