mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-01-18 21:35:11 +01:00
pay: add partial_msat
option to make partial payment.
a.k.a. "Pay with a friend!". Signed-off-by: Rusty Russell <rusty@rustcorp.com.au> Changelog-Added: JSON-RPC: `pay` has a new parameter `partial_msat` to only pay part of an invoice (someone else presumably will pay the rest at the same time!) Suggested-by: Calle
This commit is contained in:
parent
bd5d2d1673
commit
4a9b9b8b29
@ -1532,6 +1532,7 @@
|
||||
"Pay.maxfee": 11,
|
||||
"Pay.maxfeepercent": 4,
|
||||
"Pay.msatoshi": 2,
|
||||
"Pay.partial_msat": 15,
|
||||
"Pay.retry_for": 5,
|
||||
"Pay.riskfactor": 8
|
||||
},
|
||||
@ -5574,6 +5575,10 @@
|
||||
"added": "pre-v0.10.1",
|
||||
"deprecated": false
|
||||
},
|
||||
"Pay.partial_msat": {
|
||||
"added": "v23.05",
|
||||
"deprecated": false
|
||||
},
|
||||
"Pay.parts": {
|
||||
"added": "pre-v0.10.1",
|
||||
"deprecated": false
|
||||
|
BIN
cln-grpc/proto/node.proto
generated
BIN
cln-grpc/proto/node.proto
generated
Binary file not shown.
BIN
cln-grpc/src/convert.rs
generated
BIN
cln-grpc/src/convert.rs
generated
Binary file not shown.
BIN
cln-rpc/src/model.rs
generated
BIN
cln-rpc/src/model.rs
generated
Binary file not shown.
@ -25182,6 +25182,13 @@
|
||||
"description": [
|
||||
"It is only required for bolt11 invoices which do not contain a description themselves, but contain a description hash: in this case *description* is required. *description* is then checked against the hash inside the invoice before it will be paid."
|
||||
]
|
||||
},
|
||||
"partial_msat": {
|
||||
"type": "msat",
|
||||
"added": "v23.05",
|
||||
"description": [
|
||||
"Explicitly state that you are only paying some part of the invoice. Presumably someone else is paying the rest (otherwise the payment will time out at the recipient). Note that this is currently not supported for self-payment (please file an issue if you need this)"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -1085,7 +1085,7 @@ class LightningRpc(UnixDomainSocketRpc):
|
||||
def pay(self, bolt11, amount_msat=None, label=None, riskfactor=None,
|
||||
maxfeepercent=None, retry_for=None,
|
||||
maxdelay=None, exemptfee=None, localinvreqid=None, exclude=None,
|
||||
maxfee=None, description=None):
|
||||
maxfee=None, description=None, partial_msat=None):
|
||||
"""
|
||||
Send payment specified by {bolt11} with {amount_msat}
|
||||
(ignored if {bolt11} has an amount), optional {label}
|
||||
@ -1104,6 +1104,7 @@ class LightningRpc(UnixDomainSocketRpc):
|
||||
"exclude": exclude,
|
||||
"maxfee": maxfee,
|
||||
"description": description,
|
||||
"partial_msat": partial_msat,
|
||||
}
|
||||
return self.call("pay", payload)
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
@ -102,6 +102,13 @@
|
||||
"description": [
|
||||
"It is only required for bolt11 invoices which do not contain a description themselves, but contain a description hash: in this case *description* is required. *description* is then checked against the hash inside the invoice before it will be paid."
|
||||
]
|
||||
},
|
||||
"partial_msat": {
|
||||
"type": "msat",
|
||||
"added": "v23.05",
|
||||
"description": [
|
||||
"Explicitly state that you are only paying some part of the invoice. Presumably someone else is paying the rest (otherwise the payment will time out at the recipient). Note that this is currently not supported for self-payment (please file an issue if you need this)"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -1021,7 +1021,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, *maxfee;
|
||||
struct amount_msat *exemptfee, *msat, *maxfee, *partial;
|
||||
const char *label, *description;
|
||||
unsigned int *retryfor;
|
||||
u64 *riskfactor_millionths;
|
||||
@ -1054,6 +1054,7 @@ static struct command_result *json_pay(struct command *cmd,
|
||||
p_opt("exclude", param_route_exclusion_array, &exclusions),
|
||||
p_opt("maxfee", param_msat, &maxfee),
|
||||
p_opt("description", param_escaped_string, &description),
|
||||
p_opt("partial_msat", param_msat, &partial),
|
||||
p_opt_dev("dev_use_shadow", param_bool, &dev_use_shadow, true),
|
||||
NULL))
|
||||
return command_param_failed();
|
||||
@ -1199,8 +1200,21 @@ static struct command_result *json_pay(struct command *cmd,
|
||||
p->final_amount = *msat;
|
||||
}
|
||||
|
||||
/* FIXME: Allow partial payment! */
|
||||
p->our_amount = p->final_amount;
|
||||
if (partial) {
|
||||
if (amount_msat_greater(*partial, p->final_amount)) {
|
||||
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"partial_msat must be less or equal to total amount %s",
|
||||
fmt_amount_msat(tmpctx, p->final_amount));
|
||||
}
|
||||
if (node_id_eq(&my_id, p->destination)) {
|
||||
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"partial_msat not supported (yet?) for self-pay");
|
||||
}
|
||||
|
||||
p->our_amount = *partial;
|
||||
} else {
|
||||
p->our_amount = p->final_amount;
|
||||
}
|
||||
|
||||
/* We replace real final values if we're using a blinded path */
|
||||
if (p->blindedpath) {
|
||||
|
@ -5477,3 +5477,46 @@ def test_pay_routehint_minhtlc(node_factory, bitcoind):
|
||||
|
||||
# And you should also be able to getroute (and have it ignore htlc_min/max constraints!)
|
||||
l1.rpc.getroute(l3.info['id'], amount_msat=0, riskfactor=1)
|
||||
|
||||
|
||||
@pytest.mark.openchannel('v1')
|
||||
@pytest.mark.openchannel('v2')
|
||||
def test_pay_partial_msat(node_factory, executor):
|
||||
l1, l2, l3 = node_factory.line_graph(3)
|
||||
|
||||
inv = l3.rpc.invoice(100000000, "inv", "inv")
|
||||
|
||||
with pytest.raises(RpcError, match="partial_msat must be less or equal to total amount 10000000"):
|
||||
l2.rpc.pay(inv['bolt11'], partial_msat=100000001)
|
||||
|
||||
# This will fail with an MPP timeout.
|
||||
with pytest.raises(RpcError, match="failed: WIRE_MPP_TIMEOUT"):
|
||||
l2.rpc.pay(inv['bolt11'], partial_msat=90000000)
|
||||
|
||||
# This will work like normal.
|
||||
l2.rpc.pay(inv['bolt11'], partial_msat=100000000)
|
||||
|
||||
# Make sure l3 can pay to l2 now.
|
||||
wait_for(lambda: only_one(l3.rpc.listpeerchannels()['channels'])['spendable_msat'] > 1001)
|
||||
|
||||
# Now we can combine together to pay l2:
|
||||
inv = l2.rpc.invoice('any', "inv", "inv")
|
||||
|
||||
# If we specify different totals, this *won't work*
|
||||
l1pay = executor.submit(l1.rpc.pay, inv['bolt11'], amount_msat=10000, partial_msat=9000)
|
||||
l3pay = executor.submit(l3.rpc.pay, inv['bolt11'], amount_msat=10001, partial_msat=1001)
|
||||
|
||||
# BOLT #4:
|
||||
# - SHOULD fail the entire HTLC set if `total_msat` is not
|
||||
# the same for all HTLCs in the set.
|
||||
with pytest.raises(RpcError, match="failed: WIRE_FINAL_INCORRECT_HTLC_AMOUNT"):
|
||||
l3pay.result(TIMEOUT)
|
||||
with pytest.raises(RpcError, match="failed: WIRE_FINAL_INCORRECT_HTLC_AMOUNT"):
|
||||
l1pay.result(TIMEOUT)
|
||||
|
||||
# But same amount, will combine forces!
|
||||
l1pay = executor.submit(l1.rpc.pay, inv['bolt11'], amount_msat=10000, partial_msat=9000)
|
||||
l3pay = executor.submit(l3.rpc.pay, inv['bolt11'], amount_msat=10000, partial_msat=1000)
|
||||
|
||||
l1pay.result(TIMEOUT)
|
||||
l3pay.result(TIMEOUT)
|
||||
|
@ -427,7 +427,7 @@ def test_pay_plugin(node_factory):
|
||||
# Make sure usage messages are present.
|
||||
msg = 'pay bolt11 [amount_msat] [label] [riskfactor] [maxfeepercent] '\
|
||||
'[retry_for] [maxdelay] [exemptfee] [localinvreqid] [exclude] '\
|
||||
'[maxfee] [description]'
|
||||
'[maxfee] [description] [partial_msat]'
|
||||
# We run with --developer:
|
||||
msg += ' [dev_use_shadow]'
|
||||
assert only_one(l1.rpc.help('pay')['help'])['command'] == msg
|
||||
|
Loading…
Reference in New Issue
Block a user