From 620135335dbc6751e485e9e2b2822ec7d5672ff4 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Sat, 22 Jul 2023 20:56:27 +0930 Subject: [PATCH] libplugin/pay: allow shortcut for self-pay. This is the simplest solution, not the best, but there's significant risk in try to remove the "we have a path" assumption in the code pay code. Includes removing a `tal_steal` which was incorrect: the buffer has the same lifetime as the plugin, so if we steal it then things get messy when we free the struct payment. Signed-off-by: Rusty Russell Changelog-Added: Plugins: `pay` will now pay your own invoices if you try. --- doc/lightning-pay.7.md | 2 +- lightningd/pay.c | 6 ++-- plugins/libplugin-pay.c | 67 +++++++++++++++++++++++++---------------- plugins/libplugin-pay.h | 6 ++++ plugins/pay.c | 61 +++++++++++++++++++++++++++++++++---- tests/test_pay.py | 12 ++++++-- 6 files changed, 116 insertions(+), 38 deletions(-) diff --git a/doc/lightning-pay.7.md b/doc/lightning-pay.7.md index c0a02dff9..1fae8d5bf 100644 --- a/doc/lightning-pay.7.md +++ b/doc/lightning-pay.7.md @@ -119,7 +119,7 @@ The following error codes may occur: - 201: Already paid with this *hash* using different amount or destination. - 203: Permanent failure at destination. The *data* field of the error -will be routing failure object. +will be routing failure object (except for self-payment, which currently returns the error directly from lightning-sendpay(7)). - 205: Unable to find a route. - 206: Route too expensive. Either the fee or the needed total locktime for the route exceeds your *maxfeepercent* or *maxdelay* diff --git a/lightningd/pay.c b/lightningd/pay.c index 745a85bb3..045d01738 100644 --- a/lightningd/pay.c +++ b/lightningd/pay.c @@ -1156,9 +1156,9 @@ send_payment_core(struct lightningd *ld, first_hop->amount, total_msat, NULL, - take(path_secrets), - take(route_nodes), - take(route_channels), + path_secrets, + route_nodes, + route_channels, invstring, label, description, diff --git a/plugins/libplugin-pay.c b/plugins/libplugin-pay.c index 86a662b6d..a750a12f9 100644 --- a/plugins/libplugin-pay.c +++ b/plugins/libplugin-pay.c @@ -1933,6 +1933,45 @@ static void payment_notify_failure(struct payment *p, const char *error_message) plugin_notification_end(p->plugin, n); } +/* Code shared by selfpay fast-path: populate JSON output for successful + * payment, and send pay_success notification. */ +void json_add_payment_success(struct json_stream *js, + struct payment *p, + const struct preimage *preimage, + const struct payment_tree_result *result) +{ + struct json_stream *n; + struct payment *root = payment_root(p); + + json_add_node_id(js, "destination", p->destination); + json_add_sha256(js, "payment_hash", p->payment_hash); + json_add_timeabs(js, "created_at", p->start_time); + if (result) + json_add_num(js, "parts", result->attempts); + else + json_add_num(js, "parts", 1); + + json_add_amount_msat(js, "amount_msat", p->amount); + if (result) + json_add_amount_msat(js, "amount_sent_msat", result->sent); + else + json_add_amount_msat(js, "amount_sent_msat", p->amount); + + if (result && result->leafstates != PAYMENT_STEP_SUCCESS) + json_add_string(js, "warning_partial_completion", + "Some parts of the payment are not yet " + "completed, but we have the confirmation " + "from the recipient."); + json_add_preimage(js, "payment_preimage", preimage); + json_add_string(js, "status", "complete"); + + n = plugin_notification_start(p->plugin, "pay_success"); + json_add_sha256(n, "payment_hash", p->payment_hash); + if (root->invstring != NULL) + json_add_string(n, "bolt11", root->invstring); + plugin_notification_end(p->plugin, n); +} + /* This function is called whenever a payment ends up in a final state, or all * leafs in the subtree rooted in the payment are all in a final state. It is * called only once, and it is guaranteed to be called in post-order @@ -1943,8 +1982,6 @@ static void payment_finished(struct payment *p) struct json_stream *ret; struct command *cmd = p->cmd; const char *msg; - struct json_stream *n; - struct payment *root = payment_root(p); /* Either none of the leaf attempts succeeded yet, or we have a * preimage. */ @@ -1969,30 +2006,8 @@ static void payment_finished(struct payment *p) p->on_payment_success(p); ret = jsonrpc_stream_success(cmd); - json_add_node_id(ret, "destination", p->destination); - json_add_sha256(ret, "payment_hash", p->payment_hash); - json_add_timeabs(ret, "created_at", p->start_time); - json_add_num(ret, "parts", result.attempts); - - json_add_amount_msat(ret, "amount_msat", p->amount); - json_add_amount_msat(ret, "amount_sent_msat", - result.sent); - - if (result.leafstates != PAYMENT_STEP_SUCCESS) - json_add_string( - ret, "warning_partial_completion", - "Some parts of the payment are not yet " - "completed, but we have the confirmation " - "from the recipient."); - json_add_preimage(ret, "payment_preimage", result.preimage); - - json_add_string(ret, "status", "complete"); - - n = plugin_notification_start(p->plugin, "pay_success"); - json_add_sha256(n, "payment_hash", p->payment_hash); - if (root->invstring != NULL) - json_add_string(n, "bolt11", root->invstring); - plugin_notification_end(p->plugin, n); + json_add_payment_success(ret, p, result.preimage, + &result); if (command_finished(cmd, ret)) {/* Ignore result. */} p->cmd = NULL; diff --git a/plugins/libplugin-pay.h b/plugins/libplugin-pay.h index 7d019f331..06abcde63 100644 --- a/plugins/libplugin-pay.h +++ b/plugins/libplugin-pay.h @@ -484,6 +484,12 @@ void payment_abort(struct payment *p, const char *fmt, ...) PRINTF_FMT(2,3); struct payment *payment_root(struct payment *p); struct payment_tree_result payment_collect_result(struct payment *p); +/* Add fields for successful payment: result can be NULL for selfpay */ +void json_add_payment_success(struct json_stream *js, + struct payment *p, + const struct preimage *preimage, + const struct payment_tree_result *result); + /* For special effects, like inspecting your own routes. */ struct gossmap *get_gossmap(struct plugin *plugin); diff --git a/plugins/pay.c b/plugins/pay.c index ac3fe0de9..0799a2c82 100644 --- a/plugins/pay.c +++ b/plugins/pay.c @@ -799,6 +799,55 @@ static void on_payment_failure(struct payment *payment) } } +static struct command_result *selfpay_success(struct command *cmd, + const char *buf, + const jsmntok_t *result, + struct payment *p) +{ + struct json_stream *ret = jsonrpc_stream_success(cmd); + struct preimage preimage; + const char *err; + + err = json_scan(tmpctx, buf, result, + "{payment_preimage:%}", + JSON_SCAN(json_to_preimage, &preimage)); + if (err) + plugin_err(p->plugin, + "selfpay didn't have payment_preimage? %.*s", + json_tok_full_len(result), + json_tok_full(buf, result)); + json_add_payment_success(ret, p, &preimage, NULL); + return command_finished(cmd, ret); +} + +static struct command_result *selfpay(struct command *cmd, struct payment *p) +{ + struct out_req *req; + + /* This "struct payment" simply gets freed once command is done. */ + tal_steal(cmd, p); + + req = jsonrpc_request_start(cmd->plugin, cmd, "sendpay", + selfpay_success, + forward_error, p); + /* Empty route means "to-self" */ + json_array_start(req->js, "route"); + json_array_end(req->js); + json_add_sha256(req->js, "payment_hash", p->payment_hash); + if (p->label) + json_add_string(req->js, "label", p->label); + json_add_amount_msat(req->js, "amount_msat", p->amount); + json_add_string(req->js, "bolt11", p->invstring); + if (p->payment_secret) + json_add_secret(req->js, "payment_secret", p->payment_secret); + json_add_u64(req->js, "groupid", p->groupid); + if (p->payment_metadata) + json_add_hex_talarr(req->js, "payment_metadata", p->payment_metadata); + if (p->description) + json_add_string(req->js, "description", p->description); + return send_outreq(cmd->plugin, req); +} + /* We are interested in any prior attempts to pay this payment_hash / * invoice so we can set the `groupid` correctly and ensure we don't * already have a pending payment running. We also collect the summary @@ -916,6 +965,11 @@ payment_listsendpays_previous(struct command *cmd, const char *buf, p->groupid = last_group + 1; p->on_payment_success = on_payment_success; p->on_payment_failure = on_payment_failure; + + /* Bypass everything if we're doing (synchronous) self-pay */ + if (node_id_eq(&my_id, p->destination)) + return selfpay(cmd, p); + payment_start(p); return command_still_pending(cmd); } @@ -1166,13 +1220,8 @@ static struct command_result *json_pay(struct command *cmd, "This payment blinded path fee overflows!"); } - if (node_id_eq(&my_id, p->destination)) - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "This payment is destined for ourselves. " - "Self-payments are not supported"); - p->local_id = &my_id; - p->json_buffer = tal_steal(p, buf); + p->json_buffer = buf; p->json_toks = params; p->why = "Initial attempt"; p->constraints.cltv_budget = *maxdelay; diff --git a/tests/test_pay.py b/tests/test_pay.py index 356f37907..4122da3e2 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -4830,9 +4830,17 @@ def test_self_pay(node_factory): l1, l2 = node_factory.line_graph(2, wait_for_announce=True) inv = l1.rpc.invoice(10000, 'test', 'test')['bolt11'] + l1.rpc.pay(inv) - with pytest.raises(RpcError): - l1.rpc.pay(inv) + # We can pay twice, no problem! + l1.rpc.pay(inv) + + inv2 = l1.rpc.invoice(10000, 'test2', 'test2')['bolt11'] + l1.rpc.delinvoice('test2', 'unpaid') + + with pytest.raises(RpcError, match=r'Unknown invoice') as excinfo: + l1.rpc.pay(inv2) + assert excinfo.value.error['code'] == 203 @unittest.skipIf(TEST_NETWORK != 'regtest', "Canned invoice is network specific")