From 18760db66d1b2a75124818834deb18fe395dfdc6 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Sat, 22 Jul 2023 20:56:27 +0930 Subject: [PATCH] lightningd: sendpay implement zero-length path == self-pay. Previously, the "payment" and "invoice" paths were completely separate, but this now calls both. It bypasses htlc_sets (and thus, cannot do MPP), and bypasses the hook too: the former is tied closely to HTLCs, and the hook is also very htlc-centric. Includes finishing unfinished sentence in sendpay man page, as a bonus. Signed-off-by: Rusty Russell Changelog-Added: Plugins: `sendpay` now allows self-payment of invoices, by specifying an empty route. --- doc/lightning-sendpay.7.md | 11 ++-- lightningd/pay.c | 123 +++++++++++++++++++++++++++++++++++-- tests/test_pay.py | 1 - 3 files changed, 125 insertions(+), 10 deletions(-) diff --git a/doc/lightning-sendpay.7.md b/doc/lightning-sendpay.7.md index 855c4968d..287411799 100644 --- a/doc/lightning-sendpay.7.md +++ b/doc/lightning-sendpay.7.md @@ -17,18 +17,19 @@ route. Generally, a client would call lightning-getroute(7) to resolve a route, then use **sendpay** to send it. If it fails, it would call -lightning-getroute(7) again to retry. +lightning-getroute(7) again to retry. If the route is empty, a payment-to-self is attempted. The response will occur when the payment is on its way to the destination. The **sendpay** RPC command does not wait for definite -success or definite failure of the payment. Instead, use the +success or definite failure of the payment (except for already-succeeded +payments, or to-self payments). Instead, use the **waitsendpay** RPC command to poll or wait for definite success or definite failure. The *label* and *bolt11* parameters, if provided, will be returned in *waitsendpay* and *listsendpays* results. -The *amount\_msat* amount must be provided if *partid* is non-zero, otherwise +The *amount\_msat* amount must be provided if *partid* is non-zero, or the payment is to-self, otherwise it must be equal to the final amount to the destination. By default it is in millisatoshi precision; it can be a whole number, or a whole number ending in *msat* or *sat*, or a number with three decimal places ending @@ -39,10 +40,10 @@ accept the payment, as defined by the `payment_data` field in BOLT 4 and the `s` field in the BOLT 11 invoice format. It is required if *partid* is non-zero. -The *partid* value, if provided and non-zero, allows for multiple parallel +The *partid* value must not be provided for self-payments. If provided and non-zero, allows for multiple parallel partial payments with the same *payment\_hash*. The *amount\_msat* amount (which must be provided) for each **sendpay** with matching -*payment\_hash* must be equal, and **sendpay** will fail if there are +*payment\_hash* must be equal, and **sendpay** will fail if there are differing values given. The *localinvreqid* value indicates that this payment is being made for a local invoice\_request: this ensures that we only send a payment for a single-use diff --git a/lightningd/pay.c b/lightningd/pay.c index 9802be090..745a85bb3 100644 --- a/lightningd/pay.c +++ b/lightningd/pay.c @@ -1,4 +1,5 @@ #include "config.h" +#include #include #include #include @@ -11,9 +12,11 @@ #include #include #include +#include #include #include #include +#include /* Routing failure object */ struct routing_failure { @@ -1398,9 +1401,9 @@ static struct command_result *param_route_hops(struct command *cmd, size_t i; const jsmntok_t *t; - if (tok->type != JSMN_ARRAY || tok->size == 0) + if (tok->type != JSMN_ARRAY) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "%s must be an (non-empty) array", name); + "%s must be an array", name); *hops = tal_arr(cmd, struct route_hop, tok->size); json_for_each_arr(i, t, tok) { @@ -1431,6 +1434,106 @@ static struct command_result *param_route_hops(struct command *cmd, return NULL; } +/* We're paying ourselves! */ +static struct command_result *self_payment(struct lightningd *ld, + struct command *cmd, + const struct sha256 *rhash, + u64 partid, + u64 groupid, + struct amount_msat msat, + const char *label TAKES, + const char *invstring TAKES, + const char *description TAKES, + const struct sha256 *local_invreq_id, + const struct secret *payment_secret, + const u8 *payment_metadata) +{ + struct wallet_payment *payment; + const struct invoice_details *inv; + u64 inv_dbid; + const char *err; + + payment = wallet_payment_new(tmpctx, + 0, /* ID is not in db yet */ + time_now().ts.tv_sec, + NULL, + rhash, + partid, + groupid, + PAYMENT_PENDING, + &ld->id, + msat, + msat, + msat, + NULL, + NULL, + NULL, + NULL, + invstring, + label, + description, + NULL, + local_invreq_id); + + /* We write this into db immediately, but we're expected to do + * it in two stages like a normal payment. */ + wallet_payment_setup(ld->wallet, payment); + payment_store(ld, payment); + + /* Now, resolved the invoice */ + inv = invoice_check_payment(tmpctx, ld, rhash, msat, payment_secret, &err); + if (!inv) { + struct routing_failure *fail; + wallet_payment_set_status(ld->wallet, rhash, partid, groupid, + PAYMENT_FAILED, NULL); + + /* tell_waiters_failed expects one of these! */ + fail = tal(payment, struct routing_failure); + fail->failcode = WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS; + fail->erring_node = &ld->id; + fail->erring_index = 0; + fail->erring_channel = NULL; + fail->msg = NULL; + + /* Only some of these fields make sense for self payments */ + wallet_payment_set_failinfo(ld->wallet, + rhash, + partid, NULL, + true, + 0, + fail->failcode, fail->erring_node, + NULL, NULL, + err, + 0); + /* We do this even though there really can't be any waiters, + * since we didn't block. */ + tell_waiters_failed(ld, rhash, payment, PAY_DESTINATION_PERM_FAIL, + NULL, fail, err); + return sendpay_fail(cmd, payment, PAY_DESTINATION_PERM_FAIL, NULL, + fail, err); + } + + /* These should not fail, given the above succeded! */ + if (!invoices_find_by_rhash(ld->wallet->invoices, &inv_dbid, rhash) + || !invoices_resolve(ld->wallet->invoices, inv_dbid, msat)) { + log_broken(ld->log, "Could not resolve invoice %"PRIu64"!?!", inv_dbid); + return sendpay_fail(cmd, payment, PAY_DESTINATION_PERM_FAIL, NULL, NULL, "broken"); + } + + log_info(ld->log, "Self-resolved invoice '%s' with amount %s", + inv->label->s, + type_to_string(tmpctx, struct amount_msat, &msat)); + notify_invoice_payment(ld, msat, inv->r, inv->label); + + /* Now resolve the payment */ + payment_succeeded(ld, rhash, partid, groupid, &inv->r); + + /* Now the specific command which called this. */ + payment->status = PAYMENT_COMPLETE; + payment->payment_preimage = tal_dup(payment, struct preimage, &inv->r); + return sendpay_success(cmd, payment); +} + static struct command_result *json_sendpay(struct command *cmd, const char *buffer, const jsmntok_t *obj UNNEEDED, @@ -1466,14 +1569,26 @@ static struct command_result *json_sendpay(struct command *cmd, return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "Must specify msatoshi with partid"); - const struct amount_msat final_amount = route[tal_count(route)-1].amount; - /* If groupid was not provided default to incrementing from the previous one. */ if (group == NULL) { group = tal(tmpctx, u64); *group = wallet_payment_get_groupid(cmd->ld->wallet, rhash) + 1; } + if (tal_count(route) == 0) { + if (!msat) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Self-payment requires amount_msat"); + if (*partid) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Self-payment does not allow (non-zero) partid"); + return self_payment(cmd->ld, cmd, rhash, *partid, *group, *msat, + label, invstring, description, local_invreq_id, + payment_secret, payment_metadata); + } + + const struct amount_msat final_amount = route[tal_count(route)-1].amount; + if (msat && !*partid && !amount_msat_eq(*msat, final_amount)) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "Do not specify msatoshi (%s) without" diff --git a/tests/test_pay.py b/tests/test_pay.py index 666180d66..356f37907 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -5289,7 +5289,6 @@ def test_invoice_pay_desc_with_quotes(node_factory): l1.rpc.pay(invoice, description=description) -@pytest.mark.xfail(strict=True) def test_self_sendpay(node_factory): """We get much more descriptive errors from a self-payment than a remote payment, since we're not relying on a single WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS but can share more useful information""" l1 = node_factory.get_node()