From de0fd1f274c609252efe5fffc8c2e6df45b73f71 Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Wed, 8 Apr 2020 20:58:11 +0200 Subject: [PATCH] keysend: Change the plugin to add an invoice and then continue So far we were relying on `lightningd` to create an ad-hoc invoice when telling it to `resolve` with a given preimage. We now switch to having the plugin create the invoice, remove the mandatory `keysend_preimage` field (which would upset `lightningd` otherwise), and then return the modified payload with the instructions to `continue` instead of resolving. This ties back in with the existing payment/invoice handling code. Invoices are created only if we don't have a label clash (unlikely since we have the nano-time in the label), or the `payment_hash` was already used for another invoice (at which point `lightningd` will automatically reject the payment and we're a bit poorer for it, but meh :-) --- plugins/keysend.c | 112 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 86 insertions(+), 26 deletions(-) diff --git a/plugins/keysend.c b/plugins/keysend.c index c6d4065fa..c047526ab 100644 --- a/plugins/keysend.c +++ b/plugins/keysend.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -13,26 +14,58 @@ static void init(struct plugin *p, const char *buf UNUSED, static const struct plugin_command commands[] = { }; -static struct command_result *htlc_accepted_continue(struct command *cmd) +static struct command_result * +htlc_accepted_continue(struct command *cmd, struct tlv_tlv_payload *payload) { struct json_stream *response; + u8 *binpayload, *rawpayload; response = jsonrpc_stream_success(cmd); + json_add_string(response, "result", "continue"); + if (payload) { + binpayload = tal_arr(cmd, u8, 0); + towire_tlvstream_raw(&binpayload, payload->fields); + rawpayload = tal_arr(cmd, u8, 0); + towire_bigsize(&rawpayload, tal_bytelen(binpayload)); + towire(&rawpayload, binpayload, tal_bytelen(binpayload)); + json_add_string(response, "payload", tal_hex(cmd, rawpayload)); + } return command_finished(cmd, response); } -static struct command_result *htlc_accepted_resolve(struct command *cmd, - char *hexpreimage) +/* Struct wrapping the information we extract from an incoming keysend + * payment */ +struct keysend_in { + struct sha256 payment_hash; + struct preimage payment_preimage; + char *label; + struct tlv_tlv_payload *payload; + struct tlv_field *preimage_field; +}; + +static struct command_result * +htlc_accepted_invoice_created(struct command *cmd, const char *buf UNUSED, + const jsmntok_t *result UNUSED, + struct keysend_in *ki) { - struct json_stream *response; - response = jsonrpc_stream_success(cmd); - json_add_string(response, "result", "resolve"); - json_add_string(response, "payment_key", hexpreimage); - return command_finished(cmd, response); + int preimage_field_idx = ki->preimage_field - ki->payload->fields; + + /* Remove the preimage field so `lightningd` knows how to handle + * this. */ + tal_arr_remove(&ki->payload->fields, preimage_field_idx); + + /* Finally we can resolve the payment with the preimage. */ + plugin_log(cmd->plugin, LOG_INFORM, + "Resolving incoming HTLC with preimage for payment_hash %s " + "provided in the onion payload.", + tal_hexstr(tmpctx, &ki->payment_hash, sizeof(struct sha256))); + return htlc_accepted_continue(cmd, ki->payload); + } -static struct command_result *htlc_accepted_call(struct command *cmd, const char *buf, - const jsmntok_t *params) +static struct command_result *htlc_accepted_call(struct command *cmd, + const char *buf, + const jsmntok_t *params) { const jsmntok_t *payloadt = json_delve(buf, params, ".onion.payload"); const jsmntok_t *payment_hash_tok = json_delve(buf, params, ".htlc.payment_hash"); @@ -41,13 +74,15 @@ static struct command_result *htlc_accepted_call(struct command *cmd, const char struct tlv_tlv_payload *payload; struct tlv_field *preimage_field = NULL; char *hexpreimage, *hexpaymenthash; - struct sha256 payment_hash; bigsize_t s; bool unknown_even_type = false; struct tlv_field *field; + struct keysend_in *ki; + struct out_req *req; + struct timeabs now = time_now(); if (!payloadt) - return htlc_accepted_continue(cmd); + return htlc_accepted_continue(cmd, NULL); rawpayload = json_tok_bin_from_hex(cmd, buf, payloadt); max = tal_bytelen(rawpayload); @@ -55,9 +90,14 @@ static struct command_result *htlc_accepted_call(struct command *cmd, const char s = fromwire_varint(&rawpayload, &max); if (s != max) { - return htlc_accepted_continue(cmd); + return htlc_accepted_continue(cmd, NULL); + } + if (!fromwire_tlv_payload(&rawpayload, &max, payload)) { + plugin_log( + cmd->plugin, LOG_UNUSUAL, "Malformed TLV payload %.*s", + payloadt->end - payloadt->start, buf + payloadt->start); + return htlc_accepted_continue(cmd, NULL); } - fromwire_tlv_payload(&rawpayload, &max, payload); /* Try looking for the field that contains the preimage */ for (int i=0; ifields); i++) { @@ -74,7 +114,7 @@ static struct command_result *htlc_accepted_call(struct command *cmd, const char /* If we don't have a preimage field then this is not a keysend, let * someone else take care of it. */ if (preimage_field == NULL) - return htlc_accepted_continue(cmd); + return htlc_accepted_continue(cmd, NULL); if (unknown_even_type) { plugin_log(cmd->plugin, LOG_UNUSUAL, @@ -82,7 +122,7 @@ static struct command_result *htlc_accepted_call(struct command *cmd, const char ", can't safely accept the keysend. Deferring to " "other plugins.", preimage_field->numtype); - return htlc_accepted_continue(cmd); + return htlc_accepted_continue(cmd, NULL); } /* If the preimage is not 32 bytes long then we can't accept the @@ -92,15 +132,21 @@ static struct command_result *htlc_accepted_call(struct command *cmd, const char "Sender specified a preimage that is %zu bytes long, " "we expected 32 bytes. Ignoring this HTLC.", preimage_field->length); - return htlc_accepted_continue(cmd); + return htlc_accepted_continue(cmd, NULL); } + ki = tal(cmd, struct keysend_in); + memcpy(&ki->payment_preimage, preimage_field->value, 32); + ki->label = tal_fmt(ki, "keysend-%lu.%09lu", now.ts.tv_sec, now.ts.tv_nsec); + ki->payload = tal_steal(ki, payload); + ki->preimage_field = preimage_field; + hexpreimage = tal_hex(cmd, preimage_field->value); /* If the preimage doesn't hash to the payment_hash we must continue, * maybe someone else knows how to handle these. */ - sha256(&payment_hash, preimage_field->value, preimage_field->length); - hexpaymenthash = tal_hexstr(cmd, &payment_hash, sizeof(payment_hash)); + sha256(&ki->payment_hash, preimage_field->value, preimage_field->length); + hexpaymenthash = tal_hexstr(cmd, &ki->payment_hash, sizeof(ki->payment_hash)); if (!json_tok_streq(buf, payment_hash_tok, hexpaymenthash)) { plugin_log( cmd->plugin, LOG_UNUSUAL, @@ -109,15 +155,29 @@ static struct command_result *htlc_accepted_call(struct command *cmd, const char hexpreimage, hexpaymenthash, payment_hash_tok->end - payment_hash_tok->start, buf + payment_hash_tok->start); - return htlc_accepted_continue(cmd); + tal_free(ki); + return htlc_accepted_continue(cmd, NULL); } - /* Finally we can resolve the payment with the preimage. */ - plugin_log(cmd->plugin, LOG_INFORM, - "Resolving incoming HTLC with preimage for payment_hash %s " - "provided in the onion payload.", - hexpaymenthash); - return htlc_accepted_resolve(cmd, hexpreimage); + /* Now we can call `invoice` RPC to backfill an invoce matching this + * spontaneous payment, thus leaving us with an accounting + * trace. Creating the invoice is best effort: it may fail if the + * `payment_hash` is already attached to an existing invoice, and the + * label could collide (unlikely since we use the nanosecond time). If + * the call to `invoice` fails we will just continue, and `lightningd` + * will be nice and reject the payment. */ + req = jsonrpc_request_start(cmd->plugin, cmd, "invoice", + &htlc_accepted_invoice_created, + &htlc_accepted_invoice_created, + ki); + + plugin_log(cmd->plugin, LOG_INFORM, "Inserting a new invoice for keysend with payment_hash %s", hexpaymenthash); + json_add_string(req->js, "msatoshi", "any"); + json_add_string(req->js, "label", ki->label); + json_add_string(req->js, "description", "Spontaneous incoming payment through keysend"); + json_add_preimage(req->js, "preimage", &ki->payment_preimage); + + return send_outreq(cmd->plugin, req); } static const struct plugin_hook hooks[] = {