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 <rusty@rustcorp.com.au>
Changelog-Added: Plugins: `pay` will now pay your own invoices if you try.
This commit is contained in:
Rusty Russell 2023-07-22 20:56:27 +09:30
parent 18760db66d
commit 620135335d
6 changed files with 116 additions and 38 deletions

View file

@ -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*

View file

@ -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,

View file

@ -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;

View file

@ -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);

View file

@ -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;

View file

@ -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")