mirror of
https://github.com/aljazceru/lightning.git
synced 2025-12-19 15:14:23 +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:
@@ -997,7 +997,8 @@ class LightningRpc(UnixDomainSocketRpc):
|
|||||||
|
|
||||||
def pay(self, bolt11, msatoshi=None, label=None, riskfactor=None,
|
def pay(self, bolt11, msatoshi=None, label=None, riskfactor=None,
|
||||||
maxfeepercent=None, retry_for=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}
|
Send payment specified by {bolt11} with {msatoshi}
|
||||||
(ignored if {bolt11} has an amount), optional {label}
|
(ignored if {bolt11} has an amount), optional {label}
|
||||||
@@ -1014,6 +1015,7 @@ class LightningRpc(UnixDomainSocketRpc):
|
|||||||
"exemptfee": exemptfee,
|
"exemptfee": exemptfee,
|
||||||
"localofferid": localofferid,
|
"localofferid": localofferid,
|
||||||
"exclude": exclude,
|
"exclude": exclude,
|
||||||
|
"maxfee": maxfee,
|
||||||
}
|
}
|
||||||
return self.call("pay", payload)
|
return self.call("pay", payload)
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ SYNOPSIS
|
|||||||
--------
|
--------
|
||||||
|
|
||||||
**pay** *bolt11* [*msatoshi*] [*label*] [*riskfactor*]
|
**pay** *bolt11* [*msatoshi*] [*label*] [*riskfactor*]
|
||||||
[*maxfeepercent*] [*retry\_for*] [*maxdelay*] [*exemptfee*]
|
[*maxfeepercent*] [*retry_for*] [*maxdelay*] [*exemptfee*]
|
||||||
[*localofferid*] [*exclude*]
|
[*localofferid*] [*exclude*] [*maxfee*]
|
||||||
|
|
||||||
DESCRIPTION
|
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
|
that we only make a single payment for an offer, and that the offer is
|
||||||
marked `used` once paid.
|
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
|
The response will occur when the payment fails or succeeds. Once a
|
||||||
payment has succeeded, calls to **pay** with the same *bolt11* will
|
payment has succeeded, calls to **pay** with the same *bolt11* will
|
||||||
succeed immediately.
|
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;
|
char *b11_fail, *b12_fail;
|
||||||
u64 *maxfee_pct_millionths;
|
u64 *maxfee_pct_millionths;
|
||||||
u32 *maxdelay;
|
u32 *maxdelay;
|
||||||
struct amount_msat *exemptfee, *msat;
|
struct amount_msat *exemptfee, *msat, *maxfee;
|
||||||
const char *label;
|
const char *label;
|
||||||
unsigned int *retryfor;
|
unsigned int *retryfor;
|
||||||
u64 *riskfactor_millionths;
|
u64 *riskfactor_millionths;
|
||||||
@@ -950,14 +950,15 @@ static struct command_result *json_pay(struct command *cmd,
|
|||||||
p_opt("label", param_string, &label),
|
p_opt("label", param_string, &label),
|
||||||
p_opt_def("riskfactor", param_millionths,
|
p_opt_def("riskfactor", param_millionths,
|
||||||
&riskfactor_millionths, 10000000),
|
&riskfactor_millionths, 10000000),
|
||||||
p_opt_def("maxfeepercent", param_millionths,
|
p_opt("maxfeepercent", param_millionths,
|
||||||
&maxfee_pct_millionths, 500000),
|
&maxfee_pct_millionths),
|
||||||
p_opt_def("retry_for", param_number, &retryfor, 60),
|
p_opt_def("retry_for", param_number, &retryfor, 60),
|
||||||
p_opt_def("maxdelay", param_number, &maxdelay,
|
p_opt_def("maxdelay", param_number, &maxdelay,
|
||||||
maxdelay_default),
|
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("localofferid", param_sha256, &local_offer_id),
|
||||||
p_opt("exclude", param_route_exclusion_array, &exclusions),
|
p_opt("exclude", param_route_exclusion_array, &exclusions),
|
||||||
|
p_opt("maxfee", param_msat, &maxfee),
|
||||||
#if DEVELOPER
|
#if DEVELOPER
|
||||||
p_opt_def("use_shadow", param_bool, &use_shadow, true),
|
p_opt_def("use_shadow", param_bool, &use_shadow, true),
|
||||||
#endif
|
#endif
|
||||||
@@ -1103,17 +1104,35 @@ static struct command_result *json_pay(struct command *cmd,
|
|||||||
p->getroute->riskfactorppm = *riskfactor_millionths;
|
p->getroute->riskfactorppm = *riskfactor_millionths;
|
||||||
tal_free(riskfactor_millionths);
|
tal_free(riskfactor_millionths);
|
||||||
|
|
||||||
/* We free unneeded params as we use them, to keep memleak happy. */
|
if (maxfee) {
|
||||||
if (!amount_msat_fee(&p->constraints.fee_budget, p->amount, 0,
|
if (maxfee_pct_millionths || exemptfee) {
|
||||||
*maxfee_pct_millionths / 100)) {
|
return command_fail(
|
||||||
return command_fail(
|
cmd, JSONRPC2_INVALID_PARAMS,
|
||||||
cmd, JSONRPC2_INVALID_PARAMS,
|
"If you specify maxfee, cannot specify maxfeepercent or exemptfee.");
|
||||||
"Overflow when computing fee budget, fee rate too high.");
|
}
|
||||||
}
|
p->constraints.fee_budget = *maxfee;
|
||||||
tal_free(maxfee_pct_millionths);
|
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);
|
shadow_route = payment_mod_shadowroute_get_data(p);
|
||||||
payment_mod_presplit_get_data(p)->disable = disablempp;
|
payment_mod_presplit_get_data(p)->disable = disablempp;
|
||||||
payment_mod_adaptive_splitter_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(len(status) == 2)
|
||||||
assert(status[0]['failure']['code'] == 205)
|
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.
|
# This works, because fee is less than exemptfee.
|
||||||
l1.dev_pay(inv['bolt11'], msatoshi=100000, maxfeepercent=0.0001,
|
l1.dev_pay(inv['bolt11'], msatoshi=100000, maxfeepercent=0.0001,
|
||||||
exemptfee=2000, use_shadow=False)
|
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 len(status) == 1
|
||||||
assert status[0]['strategy'] == "Initial attempt"
|
assert status[0]['strategy'] == "Initial attempt"
|
||||||
|
|
||||||
|
|||||||
@@ -397,7 +397,8 @@ def test_pay_plugin(node_factory):
|
|||||||
|
|
||||||
# Make sure usage messages are present.
|
# Make sure usage messages are present.
|
||||||
msg = 'pay bolt11 [msatoshi] [label] [riskfactor] [maxfeepercent] '\
|
msg = 'pay bolt11 [msatoshi] [label] [riskfactor] [maxfeepercent] '\
|
||||||
'[retry_for] [maxdelay] [exemptfee] [localofferid] [exclude]'
|
'[retry_for] [maxdelay] [exemptfee] [localofferid] [exclude] '\
|
||||||
|
'[maxfee]'
|
||||||
if DEVELOPER:
|
if DEVELOPER:
|
||||||
msg += ' [use_shadow]'
|
msg += ' [use_shadow]'
|
||||||
assert only_one(l1.rpc.help('pay')['help'])['command'] == msg
|
assert only_one(l1.rpc.help('pay')['help'])['command'] == msg
|
||||||
|
|||||||
Reference in New Issue
Block a user