diff --git a/common/jsonrpc_errors.h b/common/jsonrpc_errors.h index ca69a9bfc0d9..96a09da2365b 100644 --- a/common/jsonrpc_errors.h +++ b/common/jsonrpc_errors.h @@ -51,6 +51,7 @@ enum jsonrpc_errcode { PAY_INSUFFICIENT_FUNDS = 215, PAY_UNREACHABLE = 216, PAY_USER_ERROR = 217, + PAY_INJECTPAYMENTONION_FAILED = 218, /* `fundchannel` or `withdraw` errors */ FUND_MAX_EXCEEDED = 300, diff --git a/contrib/msggen/msggen/schema.json b/contrib/msggen/msggen/schema.json index d0f25cdab27c..e255f365d5eb 100644 --- a/contrib/msggen/msggen/schema.json +++ b/contrib/msggen/msggen/schema.json @@ -14979,6 +14979,131 @@ } ] }, + "lightning-injectpaymentonion.json": { + "$schema": "../rpc-schema-draft.json", + "type": "object", + "additionalProperties": false, + "rpc": "injectpaymentonion", + "title": "Send a payment with a custom onion packet", + "description": [ + "The **injectpaymentonion** RPC command causes the node to receive a payment attempt similar to the way it would receive one from a peer. The onion packet is unwrapped, then handled normally: either as a local payment, or forwarded to the next peer.", + "Compared to lightning-sendonion(7): the handling of blinded paths and self-payments is trivial, and the interface blocks until the payment succeeds or fails." + ], + "request": { + "required": [ + "onion", + "payment_hash", + "amount_msat", + "cltv_expiry", + "partid", + "groupid" + ], + "properties": { + "onion": { + "type": "hex", + "description": [ + "Hex-encoded 1366 bytes long blob that was returned by either of the tools that can generate onions. It contains the payloads destined for each hop and some metadata. Please refer to [BOLT 04][bolt04] for further details. If is specific to the route that is being used and the *payment_hash* used to construct, and therefore cannot be reused for other payments or to attempt a separate route. The custom onion can generally be created using the `devtools/onion` CLI tool, or the **createonion** RPC command." + ] + }, + "payment_hash": { + "type": "hash", + "description": [ + "Specifies the 32 byte hex-encoded hash to use as a challenge to the HTLC that we are sending. It is specific to the onion and has to match the one the onion was created with." + ] + }, + "amount_msat": { + "type": "msat", + "description": [ + "The amount for the first HTLC in millisatoshis. This is also the amount which will be forwarded to the first peer (if any) as we do not charge fees on our own payments." + ] + }, + "cltv_expiry": { + "type": "u16", + "description": [ + "The cltv_expiry for the first HTLC in blocks. This must be greater than the current blockheight." + ] + }, + "partid": { + "type": "u64", + "description": [ + "The non-zero identifier for multiple parallel partial payments with the same *payment_hash*." + ] + }, + "groupid": { + "type": "u64", + "description": [ + "Grouping key to disambiguate multiple attempts to pay the same *payment_hash*. All payments in other groups must be completed before starting a new group." + ] + }, + "label": { + "type": "string", + "description": [ + "Can be used to provide a human readable reference to retrieve the payment at a later time." + ] + }, + "invstring": { + "type": "string", + "description": [ + "Usually a bolt11 or bolt12 string, which, it will be returned in *waitsendpay* and *listsendpays* results." + ] + }, + "localinvreqid": { + "type": "hash", + "description": [ + "`localinvreqid` is used by offers to link a payment attempt to a local `invoice_request` offer created by lightningd-invoicerequest(7)." + ] + } + } + }, + "response": { + "required": [ + "created_index", + "created_at", + "completed_at", + "payment_preimage" + ], + "properties": { + "created_at": { + "type": "u64", + "description": [ + "The UNIX timestamp showing when this payment was initiated." + ] + }, + "completed_at": { + "type": "u64", + "description": [ + "The UNIX timestamp showing when this payment was completed." + ] + }, + "created_index": { + "type": "u64", + "description": [ + "1-based index indicating order this payment was created in." + ] + } + } + }, + "errors": [ + "The following error codes may occur:", + "", + "- 218: injectpaymentonion failed", + "", + "The *onionreply* is returned in the error details, which can be unwrapped to discover the error" + ], + "author": [ + "Rusty Russell <> is mainly responsible." + ], + "see_also": [ + "lightning-createonion(7)", + "lightning-sendonion(7)", + "lightning-listsendpays(7)" + ], + "resources": [ + "Main web site: ", + "", + "[bolt04]: https://github.com/lightning/bolts/blob/master/04-onion-routing.md" + ] + }, "lightning-invoice.json": { "$schema": "../rpc-schema-draft.json", "type": "object", diff --git a/doc/Makefile b/doc/Makefile index 42556572da38..50c54fd1152d 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -66,6 +66,7 @@ GENERATE_MARKDOWN := doc/lightning-addgossip.7 \ doc/lightning-getroute.7 \ doc/lightning-getroutes.7 \ doc/lightning-help.7 \ + doc/lightning-injectpaymentonion.7 \ doc/lightning-invoice.7 \ doc/lightning-invoicerequest.7 \ doc/lightning-keysend.7 \ diff --git a/doc/index.rst b/doc/index.rst index bb48bce62bb6..d45018c6d768 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -76,6 +76,7 @@ Core Lightning Documentation lightning-getroutes lightning-help lightning-hsmtool + lightning-injectpaymentonion lightning-invoice lightning-invoicerequest lightning-keysend diff --git a/doc/schemas/lightning-injectpaymentonion.json b/doc/schemas/lightning-injectpaymentonion.json new file mode 100644 index 000000000000..3965b0e4a6ae --- /dev/null +++ b/doc/schemas/lightning-injectpaymentonion.json @@ -0,0 +1,125 @@ +{ + "$schema": "../rpc-schema-draft.json", + "type": "object", + "additionalProperties": false, + "rpc": "injectpaymentonion", + "title": "Send a payment with a custom onion packet", + "description": [ + "The **injectpaymentonion** RPC command causes the node to receive a payment attempt similar to the way it would receive one from a peer. The onion packet is unwrapped, then handled normally: either as a local payment, or forwarded to the next peer.", + "Compared to lightning-sendonion(7): the handling of blinded paths and self-payments is trivial, and the interface blocks until the payment succeeds or fails." + ], + "request": { + "required": [ + "onion", + "payment_hash", + "amount_msat", + "cltv_expiry", + "partid", + "groupid" + ], + "properties": { + "onion": { + "type": "hex", + "description": [ + "Hex-encoded 1366 bytes long blob that was returned by either of the tools that can generate onions. It contains the payloads destined for each hop and some metadata. Please refer to [BOLT 04][bolt04] for further details. If is specific to the route that is being used and the *payment_hash* used to construct, and therefore cannot be reused for other payments or to attempt a separate route. The custom onion can generally be created using the `devtools/onion` CLI tool, or the **createonion** RPC command." + ] + }, + "payment_hash": { + "type": "hash", + "description": [ + "Specifies the 32 byte hex-encoded hash to use as a challenge to the HTLC that we are sending. It is specific to the onion and has to match the one the onion was created with." + ] + }, + "amount_msat": { + "type": "msat", + "description": [ + "The amount for the first HTLC in millisatoshis. This is also the amount which will be forwarded to the first peer (if any) as we do not charge fees on our own payments." + ] + }, + "cltv_expiry": { + "type": "u16", + "description": [ + "The cltv_expiry for the first HTLC in blocks. This must be greater than the current blockheight." + ] + }, + "partid": { + "type": "u64", + "description": [ + "The non-zero identifier for multiple parallel partial payments with the same *payment_hash*." + ] + }, + "groupid": { + "type": "u64", + "description": [ + "Grouping key to disambiguate multiple attempts to pay the same *payment_hash*. All payments in other groups must be completed before starting a new group." + ] + }, + "label": { + "type": "string", + "description": [ + "Can be used to provide a human readable reference to retrieve the payment at a later time." + ] + }, + "invstring": { + "type": "string", + "description": [ + "Usually a bolt11 or bolt12 string, which, it will be returned in *waitsendpay* and *listsendpays* results." + ] + }, + "localinvreqid": { + "type": "hash", + "description": [ + "`localinvreqid` is used by offers to link a payment attempt to a local `invoice_request` offer created by lightningd-invoicerequest(7)." + ] + } + } + }, + "response": { + "required": [ + "created_index", + "created_at", + "completed_at", + "payment_preimage" + ], + "properties": { + "created_at": { + "type": "u64", + "description": [ + "The UNIX timestamp showing when this payment was initiated." + ] + }, + "completed_at": { + "type": "u64", + "description": [ + "The UNIX timestamp showing when this payment was completed." + ] + }, + "created_index": { + "type": "u64", + "description": [ + "1-based index indicating order this payment was created in." + ] + } + } + }, + "errors": [ + "The following error codes may occur:", + "", + "- 218: injectpaymentonion failed", + "", + "The *onionreply* is returned in the error details, which can be unwrapped to discover the error" + ], + "author": [ + "Rusty Russell <> is mainly responsible." + ], + "see_also": [ + "lightning-createonion(7)", + "lightning-sendonion(7)", + "lightning-listsendpays(7)" + ], + "resources": [ + "Main web site: ", + "", + "[bolt04]: https://github.com/lightning/bolts/blob/master/04-onion-routing.md" + ] +} diff --git a/lightningd/htlc_set.c b/lightningd/htlc_set.c index 56483f2da5bd..47677678d28a 100644 --- a/lightningd/htlc_set.c +++ b/lightningd/htlc_set.c @@ -1,21 +1,39 @@ #include "config.h" #include #include +#include #include #include #include #include #include -/* If an HTLC times out, we need to free entire set, since we could be processing - * it in invoice.c right now. */ -static void htlc_set_hin_destroyed(struct htlc_in *hin, - struct htlc_set *set) +static struct incoming_payment *new_inpay(const tal_t *ctx, + struct logger *log, + struct amount_msat msat, + void (*fail)(void *, const u8 *), + void (*succeeded)(void *, + const struct preimage *), + void *arg) { - for (size_t i = 0; i < tal_count(set->htlcs); i++) { - if (set->htlcs[i] == hin) { + struct incoming_payment *inpay = tal(ctx, struct incoming_payment); + inpay->log = log; + inpay->msat = msat; + inpay->fail = fail; + inpay->succeeded = succeeded; + inpay->arg = arg; + return inpay; +} + +/* If an HTLC times out, we need to free entire set, since we could be + * processing it in invoice.c right now. */ +static void htlc_set_inpay_destroyed(struct incoming_payment *inpay, + struct htlc_set *set) +{ + for (size_t i = 0; i < tal_count(set->inpays); i++) { + if (set->inpays[i] == inpay) { /* Don't try to re-fail this HTLC! */ - tal_arr_remove(&set->htlcs, i); + tal_arr_remove(&set->inpays, i); /* Kind of the correct failure code. */ htlc_set_fail(set, take(towire_mpp_timeout(NULL))); return; @@ -41,46 +59,63 @@ static void timeout_htlc_set(struct htlc_set *set) htlc_set_fail(set, take(towire_mpp_timeout(NULL))); } -void htlc_set_fail(struct htlc_set *set, const u8 *failmsg TAKES) +void htlc_set_fail_(struct htlc_set *set, const u8 *failmsg TAKES, + const char *file, int line) { /* Don't let local_fail_in_htlc take! */ if (taken(failmsg)) tal_steal(set, failmsg); - for (size_t i = 0; i < tal_count(set->htlcs); i++) { + for (size_t i = 0; i < tal_count(set->inpays); i++) { + const u8 *this_failmsg; + /* Don't remove from set */ - tal_del_destructor2(set->htlcs[i], htlc_set_hin_destroyed, set); - local_fail_in_htlc(set->htlcs[i], failmsg); + tal_del_destructor2(set->inpays[i], htlc_set_inpay_destroyed, set); + + if (tal_bytelen(failmsg) == 0) + this_failmsg = towire_incorrect_or_unknown_payment_details(tmpctx, set->inpays[i]->msat, get_block_height(set->ld->topology)); + else + this_failmsg = failmsg; + + log_debug(set->inpays[i]->log, + "failing with %s: %s:%u", + onion_wire_name(fromwire_peektype(this_failmsg)), + file, line); + /* Attach inpays[i] to set so it's freed below (not with arg) */ + tal_steal(set->inpays, set->inpays[i]); + set->inpays[i]->fail(set->inpays[i]->arg, this_failmsg); } tal_free(set); } void htlc_set_fulfill(struct htlc_set *set, const struct preimage *preimage) { - for (size_t i = 0; i < tal_count(set->htlcs); i++) { + for (size_t i = 0; i < tal_count(set->inpays); i++) { /* Don't remove from set */ - tal_del_destructor2(set->htlcs[i], htlc_set_hin_destroyed, set); + tal_del_destructor2(set->inpays[i], + htlc_set_inpay_destroyed, set); - /* mark that we filled -- needed for tagging coin mvt */ - set->htlcs[i]->we_filled = tal(set->htlcs[i], bool); - *set->htlcs[i]->we_filled = true; - fulfill_htlc(set->htlcs[i], preimage); + /* Reparent set->inpays[i] so it's freed with set */ + tal_steal(set->inpays, set->inpays[i]); + set->inpays[i]->succeeded(set->inpays[i]->arg, preimage); } tal_free(set); } static struct htlc_set *new_htlc_set(struct lightningd *ld, - struct htlc_in *hin, + struct incoming_payment *inpay, + const struct sha256 *payment_hash, struct amount_msat total_msat) { struct htlc_set *set; set = tal(ld, struct htlc_set); + set->ld = ld; set->total_msat = total_msat; - set->payment_hash = hin->payment_hash; + set->payment_hash = *payment_hash; set->so_far = AMOUNT_MSAT(0); - set->htlcs = tal_arr(set, struct htlc_in *, 1); - set->htlcs[0] = hin; + set->inpays = tal_arr(set, struct incoming_payment *, 1); + set->inpays[0] = inpay; /* BOLT #4: * - MUST fail all HTLCs in the HTLC set after some reasonable @@ -95,11 +130,17 @@ static struct htlc_set *new_htlc_set(struct lightningd *ld, return set; } -void htlc_set_add(struct lightningd *ld, - struct htlc_in *hin, - struct amount_msat total_msat, - const struct secret *payment_secret) +void htlc_set_add_(struct lightningd *ld, + struct logger *log, + struct amount_msat msat, + struct amount_msat total_msat, + const struct sha256 *payment_hash, + const struct secret *payment_secret, + void (*fail)(void *, const u8 *), + void (*succeeded)(void *, const struct preimage *), + void *arg) { + struct incoming_payment *inpay; struct htlc_set *set; const struct invoice_details *details; const char *err; @@ -110,22 +151,21 @@ void htlc_set_add(struct lightningd *ld, * [Failure Messages](#failure-messages) * - Note: "amount paid" specified there is the `total_msat` field. */ - details = invoice_check_payment(tmpctx, ld, &hin->payment_hash, + details = invoice_check_payment(tmpctx, ld, payment_hash, total_msat, payment_secret, &err); if (!details) { - log_debug(hin->key.channel->log, "payment failed: %s", err); - local_fail_in_htlc(hin, - take(failmsg_incorrect_or_unknown(NULL, ld, hin))); + log_debug(log, "payment failed: %s", err); + fail(arg, take(failmsg_incorrect_or_unknown(NULL, ld, msat))); return; } /* If we insist on a payment secret, it must always have it */ if (feature_is_set(details->features, COMPULSORY_FEATURE(OPT_PAYMENT_SECRET)) && !payment_secret) { - log_debug(ld->log, "Missing payment_secret, but required for %s", - fmt_sha256(tmpctx, &hin->payment_hash)); - local_fail_in_htlc(hin, - take(failmsg_incorrect_or_unknown(NULL, ld, hin))); + log_debug(log, + "Missing payment_secret, but required for %s", + fmt_sha256(tmpctx, payment_hash)); + fail(arg, take(failmsg_incorrect_or_unknown(NULL, ld, msat))); return; } @@ -133,9 +173,10 @@ void htlc_set_add(struct lightningd *ld, * - otherwise, if it supports `basic_mpp`: * - MUST add it to the HTLC set corresponding to that `payment_hash`. */ - set = htlc_set_map_get(ld->htlc_sets, &hin->payment_hash); + inpay = new_inpay(arg, log, msat, fail, succeeded, arg); + set = htlc_set_map_get(ld->htlc_sets, payment_hash); if (!set) - set = new_htlc_set(ld, hin, total_msat); + set = new_htlc_set(ld, inpay, payment_hash, total_msat); else { /* BOLT #4: * @@ -149,46 +190,48 @@ void htlc_set_add(struct lightningd *ld, /* We check this now, since we want to fail with this as soon * as possible, to avoid other probing attacks. */ if (!payment_secret) { - local_fail_in_htlc(hin, take(failmsg_incorrect_or_unknown(NULL, ld, hin))); + log_debug(log, + "Missing payment_secret, but required for MPP"); + fail(arg, take(failmsg_incorrect_or_unknown(NULL, ld, msat))); return; } - tal_arr_expand(&set->htlcs, hin); + tal_arr_expand(&set->inpays, inpay); } /* Remove from set should hin get destroyed somehow */ - tal_add_destructor2(hin, htlc_set_hin_destroyed, set); + tal_add_destructor2(inpay, htlc_set_inpay_destroyed, set); /* BOLT #4: * - SHOULD fail the entire HTLC set if `total_msat` is not * the same for all HTLCs in the set. */ if (!amount_msat_eq(total_msat, set->total_msat)) { - log_unusual(ld->log, "Failing HTLC set %s:" + log_unusual(log, "Failing HTLC set %s:" " total_msat %s new htlc total %s", fmt_sha256(tmpctx, &set->payment_hash), fmt_amount_msat(tmpctx, set->total_msat), fmt_amount_msat(tmpctx, total_msat)); htlc_set_fail(set, take(towire_final_incorrect_htlc_amount(NULL, - hin->msat))); + msat))); return; } - if (!amount_msat_accumulate(&set->so_far, hin->msat)) { + if (!amount_msat_accumulate(&set->so_far, msat)) { log_unusual(ld->log, "Failing HTLC set %s:" " overflow adding %s+%s", fmt_sha256(tmpctx, &set->payment_hash), fmt_amount_msat(tmpctx, set->so_far), - fmt_amount_msat(tmpctx, hin->msat)); + fmt_amount_msat(tmpctx, msat)); htlc_set_fail(set, take(towire_final_incorrect_htlc_amount(NULL, - hin->msat))); + msat))); return; } log_debug(ld->log, "HTLC set contains %zu HTLCs, for a total of %s out of %s (%spayment_secret)", - tal_count(set->htlcs), + tal_count(set->inpays), fmt_amount_msat(tmpctx, set->so_far), fmt_amount_msat(tmpctx, total_msat), payment_secret ? "" : "no " @@ -214,8 +257,7 @@ void htlc_set_add(struct lightningd *ld, * - MUST require `payment_secret` for all HTLCs in the set. */ /* This catches the case of the first payment in a set. */ if (!payment_secret) { - htlc_set_fail(set, - take(failmsg_incorrect_or_unknown(NULL, ld, hin))); + htlc_set_fail(set, NULL); return; } } diff --git a/lightningd/htlc_set.h b/lightningd/htlc_set.h index 573a92d07083..4bc44bad5ee3 100644 --- a/lightningd/htlc_set.h +++ b/lightningd/htlc_set.h @@ -9,14 +9,28 @@ #include #include -struct htlc_in; struct lightningd; +struct logger; + +/* Could be an incoming HTLC, could be a local payment */ +struct incoming_payment { + /* Where to log */ + struct logger *log; + /* Amount of this payment */ + struct amount_msat msat; + /* If it fails */ + void (*fail)(void *arg, const u8 *failmsg TAKES); + /* If it succeeded: here's the preimage. */ + void (*succeeded)(void *arg, const struct preimage *preimage); + void *arg; +}; /* Set of incoming HTLCs for multi-part-payments */ struct htlc_set { + struct lightningd *ld; struct amount_msat total_msat, so_far; struct sha256 payment_hash; - struct htlc_in **htlcs; + struct incoming_payment **inpays; struct oneshot *timeout; }; @@ -42,14 +56,35 @@ HTABLE_DEFINE_TYPE(struct htlc_set, htlc_set_eq, htlc_set_map); -/* Handles hin: if it completes a set, hands that to invoice_try_pay */ -void htlc_set_add(struct lightningd *ld, - struct htlc_in *hin, - struct amount_msat total_msat, - const struct secret *payment_secret); +/* Handles arg: if it completes a set, calls invoice_try_pay */ +void htlc_set_add_(struct lightningd *ld, + struct logger *log, + struct amount_msat msat, + struct amount_msat total_msat, + const struct sha256 *payment_hash, + const struct secret *payment_secret, + void (*fail)(void *, const u8 *), + void (*succeeded)(void *, const struct preimage *), + void *arg); + +#define htlc_set_add(ld, log, msat, total_msat, payment_hash, payment_secret, \ + fail, succeeded, arg) \ + htlc_set_add_((ld), (log), (msat), (total_msat), (payment_hash), \ + (payment_secret), \ + typesafe_cb_postargs(void, void *, \ + (fail), (arg), \ + const u8 *), \ + typesafe_cb_postargs(void, void *, \ + (succeeded), (arg), \ + const struct preimage *), \ + (arg)) -/* Fail every htlc in the set: frees set */ -void htlc_set_fail(struct htlc_set *set, const u8 *failmsg TAKES); +/* Fail every htlc in the set: frees set. If failmsg is NULL/zero-length, + * it sends each one a WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS. */ +#define htlc_set_fail(set, failmsg) \ + htlc_set_fail_((set), (failmsg), __FILE__, __LINE__) +void htlc_set_fail_(struct htlc_set *set, const u8 *failmsg TAKES, + const char *file, int line); /* Fulfill every htlc in the set: frees set */ void htlc_set_fulfill(struct htlc_set *set, const struct preimage *preimage); diff --git a/lightningd/invoice.c b/lightningd/invoice.c index 265dc212212d..52f283984b3c 100644 --- a/lightningd/invoice.c +++ b/lightningd/invoice.c @@ -176,10 +176,14 @@ static void invoice_payment_add_tlvs(struct json_stream *stream, { struct htlc_in *hin; const struct tlv_payload *tlvs; - assert(tal_count(hset->htlcs) > 0); + assert(tal_count(hset->inpays) > 0); + + /* Only do this if it's actually an HTLC */ + if ((void *)hset->inpays[0]->fail != (void *)local_fail_in_htlc) + return; /* Pick the first HTLC as representative for the entire set. */ - hin = hset->htlcs[0]; + hin = hset->inpays[0]->arg; tlvs = hin->payload->tlv; @@ -223,9 +227,9 @@ static void invoice_payload_remove_set(struct htlc_set *set, payload->set = NULL; } +/* Returns magic value to send generic incorrect_or_unknown_payment_details */ static const u8 *hook_gives_failmsg(const tal_t *ctx, struct lightningd *ld, - const struct htlc_in *hin, const char *buffer, const jsmntok_t *toks) { @@ -242,7 +246,9 @@ static const u8 *hook_gives_failmsg(const tal_t *ctx, if (json_tok_streq(buffer, resulttok, "continue")) { return NULL; } else if (json_tok_streq(buffer, resulttok, "reject")) { - return failmsg_incorrect_or_unknown(ctx, ld, hin); + /* htlc_set_fail makes this a per-htlc + * incorrect_or_unknown_payment_details */ + return tal_arr(ctx, u8, 0); } else fatal("Invalid invoice_payment hook result: %.*s", toks[0].end - toks[0].start, buffer); @@ -274,8 +280,7 @@ invoice_payment_hooks_done(struct invoice_payment_hook_payload *payload STEALS) /* If invoice gets paid meanwhile (plugin responds out-of-order?) then * we can also fail */ if (!invoices_find_by_label(ld->wallet->invoices, &inv_dbid, payload->label)) { - htlc_set_fail(payload->set, take(failmsg_incorrect_or_unknown( - NULL, ld, payload->set->htlcs[0]))); + htlc_set_fail(payload->set, NULL); return; } @@ -283,15 +288,14 @@ invoice_payment_hooks_done(struct invoice_payment_hook_payload *payload STEALS) if (!invoices_resolve(ld->wallet->invoices, inv_dbid, payload->msat, payload->label, payload->outpoint)) { if (payload->set) - htlc_set_fail(payload->set, take(failmsg_incorrect_or_unknown( - NULL, ld, payload->set->htlcs[0]))); + htlc_set_fail(payload->set, NULL); return; } log_info(ld->log, "Resolved invoice '%s' with amount %s in %zu htlcs", payload->label->s, fmt_amount_msat(tmpctx, payload->msat), - payload->set ? tal_count(payload->set->htlcs) : 0); + payload->set ? tal_count(payload->set->inpays) : 0); if (payload->set) htlc_set_fulfill(payload->set, &payload->preimage); @@ -316,8 +320,7 @@ invoice_payment_deserialize(struct invoice_payment_hook_payload *payload, if (payload->set) { /* Did we have a hook result? */ - failmsg = hook_gives_failmsg(NULL, ld, - payload->set->htlcs[0], buffer, toks); + failmsg = hook_gives_failmsg(NULL, ld, buffer, toks); if (failmsg) { htlc_set_fail(payload->set, take(failmsg)); return false; diff --git a/lightningd/pay.c b/lightningd/pay.c index ac72e42d8fc2..b1f961e7aa7d 100644 --- a/lightningd/pay.c +++ b/lightningd/pay.c @@ -2,10 +2,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -37,6 +39,18 @@ struct waitsendpay_command { u64 partid; u64 groupid; struct command *cmd; + void *arg; + + struct command_result *(*success)(struct command *cmd, + const struct wallet_payment *payment, + void *arg); + struct command_result *(*fail)(struct command *cmd, + const struct wallet_payment *payment, + enum jsonrpc_errcode pay_errcode, + const struct onionreply *onionreply, + const struct routing_failure *fail, + const char *errmsg, + void *arg); }; static bool string_to_payment_status(const char *status_str, size_t len, @@ -78,10 +92,21 @@ static void destroy_waitsendpay_command(struct waitsendpay_command *pc) /* Owned by cmd, if cmd is deleted, then sendpay_success/sendpay_fail will * no longer be called. */ static void -add_waitsendpay_waiter(struct lightningd *ld, - struct command *cmd, - const struct sha256 *payment_hash, - u64 partid, u64 groupid) +add_waitsendpay_waiter_(struct lightningd *ld, + struct command *cmd, + const struct sha256 *payment_hash, + u64 partid, u64 groupid, + struct command_result *(*success)(struct command *cmd, + const struct wallet_payment *payment, + void *arg), + struct command_result *(*fail)(struct command *cmd, + const struct wallet_payment *payment, + enum jsonrpc_errcode pay_errcode, + const struct onionreply *onionreply, + const struct routing_failure *fail, + const char *errmsg, + void *arg), + void *arg) { struct waitsendpay_command *pc = tal(cmd, struct waitsendpay_command); @@ -89,10 +114,29 @@ add_waitsendpay_waiter(struct lightningd *ld, pc->partid = partid; pc->groupid = groupid; pc->cmd = cmd; + pc->arg = arg; + pc->success = success; + pc->fail = fail; list_add(&ld->waitsendpay_commands, &pc->list); tal_add_destructor(pc, destroy_waitsendpay_command); } +#define add_waitsendpay_waiter(ld, cmd, payment_hash, partid, groupid, success, fail, arg) \ + add_waitsendpay_waiter_((ld), (cmd), (payment_hash), (partid), (groupid), \ + typesafe_cb_preargs(struct command_result *, void *, \ + (success), (arg), \ + struct command *, \ + const struct wallet_payment *), \ + typesafe_cb_preargs(struct command_result *, void *, \ + (fail), (arg), \ + struct command *, \ + const struct wallet_payment *, \ + enum jsonrpc_errcode, \ + const struct onionreply *, \ + const struct routing_failure *, \ + const char *), \ + (arg)) + /* Outputs fields, not a separate object*/ void json_add_payment_fields(struct json_stream *response, const struct wallet_payment *t) @@ -149,7 +193,8 @@ void json_add_payment_fields(struct json_stream *response, } static struct command_result *sendpay_success(struct command *cmd, - const struct wallet_payment *payment) + const struct wallet_payment *payment, + void *unused) { struct json_stream *response; @@ -232,7 +277,8 @@ sendpay_fail(struct command *cmd, enum jsonrpc_errcode pay_errcode, const struct onionreply *onionreply, const struct routing_failure *fail, - const char *errmsg) + const char *errmsg, + void *unused) { struct json_stream *data; @@ -272,7 +318,7 @@ static void tell_waiters_failed(struct lightningd *ld, const char *errmsg = sendpay_errmsg_fmt(tmpctx, pay_errcode, fail, details); - /* Careful: sendpay_fail deletes cmd */ + /* Careful: ->fail deletes cmd */ list_for_each_safe(&ld->waitsendpay_commands, pc, next, list) { if (!sha256_eq(payment_hash, &pc->payment_hash)) continue; @@ -281,8 +327,7 @@ static void tell_waiters_failed(struct lightningd *ld, if (payment->groupid != pc->groupid) continue; - sendpay_fail(pc->cmd, payment, pay_errcode, onionreply, fail, - errmsg); + pc->fail(pc->cmd, payment, pay_errcode, onionreply, fail, errmsg, pc->arg); } notify_sendpay_failure(ld, @@ -309,7 +354,7 @@ static void tell_waiters_success(struct lightningd *ld, if (payment->groupid != pc->groupid) continue; - sendpay_success(pc->cmd, payment); + pc->success(pc->cmd, payment, pc->arg); } notify_sendpay_success(ld, payment); } @@ -367,7 +412,6 @@ immediate_routing_failure(const tal_t *ctx, static struct routing_failure* local_routing_failure(const tal_t *ctx, const struct lightningd *ld, - const struct htlc_out *hout, enum onion_wire failcode, const struct wallet_payment *payment) { @@ -392,9 +436,6 @@ local_routing_failure(const tal_t *ctx, routing_failure->msg = NULL; - log_debug(hout->key.channel->log, "local_routing_failure: %u (%s)", - routing_failure->failcode, - onion_wire_name(routing_failure->failcode)); return routing_failure; } @@ -502,28 +543,32 @@ remote_routing_failure(const tal_t *ctx, return routing_failure; } -void payment_failed(struct lightningd *ld, const struct htlc_out *hout, +void payment_failed(struct lightningd *ld, + struct logger *log, + const struct sha256 *payment_hash, + u64 partid, u64 groupid, + const struct onionreply *failonion, + const u8 *failmsg, const char *localfail) { struct wallet_payment *payment; struct routing_failure* fail = NULL; const char *failstr; enum jsonrpc_errcode pay_errcode; - const u8 *failmsg; int origin_index; payment = wallet_payment_by_hash(tmpctx, ld->wallet, - &hout->payment_hash, - hout->partid, hout->groupid); + payment_hash, + partid, groupid); #ifdef COMPAT_V052 /* Prior to "pay: delete HTLC when we delete payment." we would * delete a payment on retry, but leave the HTLC. */ if (!payment) { - log_unusual(hout->key.channel->log, + log_unusual(log, "No payment for %s:" " was this an old database?", - fmt_sha256(tmpctx, &hout->payment_hash)); + fmt_sha256(tmpctx, payment_hash)); return; } #else @@ -535,26 +580,25 @@ void payment_failed(struct lightningd *ld, const struct htlc_out *hout, if (localfail) { /* Use temporary_channel_failure if failmsg has it */ enum onion_wire failcode; - failcode = fromwire_peektype(hout->failmsg); + failcode = fromwire_peektype(failmsg); - fail = local_routing_failure(tmpctx, ld, hout, failcode, - payment); + fail = local_routing_failure(tmpctx, ld, failcode, payment); + log_debug(log, "local_routing_failure: %u (%s)", + fail->failcode, + onion_wire_name(fail->failcode)); failstr = localfail; pay_errcode = PAY_TRY_OTHER_ROUTE; } else if (payment->path_secrets == NULL) { - /* This was a payment initiated with `sendonion`, we therefore + /* This was a payment initiated with `sendonion`/`injectonionmessage`, we therefore * don't have the path secrets and cannot decode the error - * onion. Let's store it and hope whatever called `sendonion` - * knows how to deal with these. */ - + * onion. We hand it to the user. */ pay_errcode = PAY_UNPARSEABLE_ONION; fail = NULL; failstr = NULL; - } else if (hout->failmsg) { + } else if (failmsg) { /* This can happen when a direct peer told channeld it's a * malformed onion using update_fail_malformed_htlc. */ failstr = "local failure"; - failmsg = hout->failmsg; origin_index = 0; pay_errcode = PAY_TRY_OTHER_ROUTE; goto use_failmsg; @@ -566,12 +610,11 @@ void payment_failed(struct lightningd *ld, const struct htlc_out *hout, failmsg = unwrap_onionreply(tmpctx, path_secrets, tal_count(path_secrets), - hout->failonion, &origin_index); + failonion, &origin_index); if (!failmsg) { - log_info(hout->key.channel->log, - "htlc %"PRIu64" failed with bad reply (%s)", - hout->key.id, - tal_hex(tmpctx, hout->failonion->contents)); + log_info(log, + "htlc failed with bad reply (%s)", + tal_hex(tmpctx, failonion->contents)); /* Cannot record failure. */ fail = NULL; pay_errcode = PAY_UNPARSEABLE_ONION; @@ -580,28 +623,27 @@ void payment_failed(struct lightningd *ld, const struct htlc_out *hout, use_failmsg: failcode = fromwire_peektype(failmsg); - log_info(hout->key.channel->log, - "htlc %"PRIu64" " - "failed from %ith node " + log_info(log, + "htlc failed from %ith node " "with code 0x%04x (%s)", - hout->key.id, origin_index, failcode, onion_wire_name(failcode)); fail = remote_routing_failure(tmpctx, ld, payment, failmsg, origin_index, - hout->key.channel->log, + log, &pay_errcode); } } - wallet_payment_set_status(ld->wallet, &hout->payment_hash, - hout->partid, hout->groupid, + wallet_payment_set_status(ld->wallet, payment_hash, + partid, groupid, PAYMENT_FAILED, NULL); wallet_payment_set_failinfo(ld->wallet, - &hout->payment_hash, - hout->partid, - fail ? NULL : hout->failonion, + payment_hash, + partid, + /* We only save failonion if it's unparseable */ + fail ? NULL : failonion, pay_errcode == PAY_DESTINATION_PERM_FAIL, fail ? fail->erring_index : -1, fail ? fail->failcode : 0, @@ -611,8 +653,8 @@ void payment_failed(struct lightningd *ld, const struct htlc_out *hout, failstr, fail ? fail->channel_dir : 0); - tell_waiters_failed(ld, &hout->payment_hash, payment, pay_errcode, - hout->failonion, fail, failstr); + tell_waiters_failed(ld, payment_hash, payment, pay_errcode, + failonion, fail, failstr); } /* Wait for a payment. If cmd is deleted, then wait_payment() @@ -651,11 +693,12 @@ static struct command_result *wait_payment(struct lightningd *ld, switch (payment->status) { case PAYMENT_PENDING: - add_waitsendpay_waiter(ld, cmd, payment_hash, partid, groupid); + add_waitsendpay_waiter(ld, cmd, payment_hash, partid, groupid, + sendpay_success, sendpay_fail, NULL); return NULL; case PAYMENT_COMPLETE: - return sendpay_success(cmd, payment); + return sendpay_success(cmd, payment, NULL); case PAYMENT_FAILED: /* Get error from DB */ @@ -682,7 +725,8 @@ static struct command_result *wait_payment(struct lightningd *ld, cmd, payment, PAY_UNPARSEABLE_ONION, failonionreply, NULL, sendpay_errmsg_fmt(tmpctx, PAY_UNPARSEABLE_ONION, - NULL, faildetail)); + NULL, faildetail), + NULL); } else { /* Parsed onion error, get its details */ assert(failnode); @@ -709,7 +753,8 @@ static struct command_result *wait_payment(struct lightningd *ld, return sendpay_fail( cmd, payment, rpcerrorcode, NULL, fail, sendpay_errmsg_fmt(tmpctx, rpcerrorcode, fail, - faildetail)); + faildetail), + NULL); } } @@ -896,7 +941,7 @@ static struct command_result *check_progress(struct lightningd *ld, fmt_node_id(tmpctx, payment->destination)); } - return sendpay_success(cmd, payment); + return sendpay_success(cmd, payment, NULL); case PAYMENT_PENDING: /* At most one payment group can be in-flight at any @@ -1090,7 +1135,8 @@ send_payment_core(struct lightningd *ld, return sendpay_fail( cmd, NULL, PAY_TRY_OTHER_ROUTE, NULL, fail, sendpay_errmsg_fmt(tmpctx, PAY_TRY_OTHER_ROUTE, fail, - "First peer not ready")); + "First peer not ready"), + NULL); } payment = wallet_add_payment(cmd, @@ -1458,14 +1504,14 @@ static struct command_result *self_payment(struct lightningd *ld, 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); + fail, err, NULL); } /* 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, inv->label, NULL)) { log_broken(ld->log, "Could not resolve invoice %"PRIu64"!?!", inv_dbid); - return sendpay_fail(cmd, payment, PAY_DESTINATION_PERM_FAIL, NULL, NULL, "broken"); + return sendpay_fail(cmd, payment, PAY_DESTINATION_PERM_FAIL, NULL, NULL, "broken", NULL); } log_info(ld->log, "Self-resolved invoice '%s' with amount %s", @@ -1479,7 +1525,7 @@ static struct command_result *self_payment(struct lightningd *ld, /* 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); + return sendpay_success(cmd, payment, NULL); } static struct command_result *json_sendpay(struct command *cmd, @@ -1626,6 +1672,389 @@ static const struct json_command waitsendpay_command = { }; AUTODATA(json_command, &waitsendpay_command); +static struct command_result * +injectonion_fail(struct command *cmd, + const struct wallet_payment *payment, + enum jsonrpc_errcode pay_errcode, + const struct onionreply *onionreply, + const struct routing_failure *fail, + const char *errmsg, + struct secret *shared_secret) +{ + struct json_stream *js; + + /* Turn local errors into onion reply. */ + if (!onionreply) + onionreply = create_onionreply(tmpctx, shared_secret, fail->msg); + + js = json_stream_fail(cmd, PAY_INJECTPAYMENTONION_FAILED, errmsg); + /* We wrap the onion reply, as it expects. */ + json_add_hex_talarr(js, "onionreply", + wrap_onionreply(tmpctx, shared_secret, onionreply) + ->contents); + + json_object_end(js); + return command_failed(cmd, js); +} + +static struct command_result * +injectonion_succeed(struct command *cmd, + const struct wallet_payment *payment, + void *unused) +{ + struct json_stream *response = json_stream_success(cmd); + + assert(payment->status == PAYMENT_COMPLETE); + + json_add_u64(response, "created_index", payment->id); + json_add_u32(response, "created_at", payment->timestamp); + json_add_u32(response, "completed_at", *payment->completed_at); + json_add_preimage(response, "payment_preimage", payment->payment_preimage); + return command_success(cmd, response); +} + +struct selfpay { + struct command *cmd; + struct secret shared_secret; + u64 partid, groupid; + struct sha256 payment_hash; +}; + +/* FIXME: Map errors better using payment_failed? */ +static void selfpay_mpp_fail(struct selfpay *selfpay, const u8 *failmsg TAKES) +{ + struct onionreply *reply = create_onionreply(tmpctx, &selfpay->shared_secret, failmsg); + + if (taken(failmsg)) + tal_steal(selfpay->cmd, failmsg); + + payment_failed(selfpay->cmd->ld, + selfpay->cmd->ld->log, + &selfpay->payment_hash, + selfpay->partid, + selfpay->groupid, + reply, + NULL, + NULL); +} + +static void selfpay_mpp_succeeded(struct selfpay *selfpay, + const struct preimage *preimage) +{ + payment_succeeded(selfpay->cmd->ld, + &selfpay->payment_hash, + selfpay->partid, + selfpay->groupid, + preimage); +} + +static struct command_result *param_u64_nonzero(struct command *cmd, + const char *name, + const char *buffer, + const jsmntok_t *tok, + u64 **val) +{ + struct command_result *res = param_u64(cmd, name, buffer, tok, val); + if (res == NULL && *val == 0) + res = command_fail_badparam(cmd, name, buffer, tok, + "Must be non-zero"); + return res; +} + +static void register_payment_and_waiter(struct command *cmd, + const struct sha256 *payment_hash, + u64 partid, u64 groupid, + struct amount_msat msat, + struct amount_msat msat_sent, + struct amount_msat total_msat, + const char *label, + const char *invstring, + struct sha256 *local_invreq_id, + const struct secret *shared_secret) +{ + wallet_add_payment(cmd, + cmd->ld->wallet, + time_now().ts.tv_sec, + NULL, + payment_hash, + partid, + groupid, + PAYMENT_PENDING, + NULL, + msat, + msat_sent, + total_msat, + NULL, + NULL, + NULL, + NULL, + invstring, + label, + NULL, + NULL, + local_invreq_id); + + /* Now we wait for htlc to resolve (it will need shared_secret!) */ + add_waitsendpay_waiter(cmd->ld, cmd, payment_hash, partid, groupid, + injectonion_succeed, injectonion_fail, + tal_dup(cmd, struct secret, shared_secret)); +} + +static struct command_result *json_injectpaymentonion(struct command *cmd, + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) +{ + u8 *onion; + enum onion_wire failcode; + struct sha256 *payment_hash; + struct lightningd *ld = cmd->ld; + const char *label, *invstring; + struct pubkey *blinding, *next_path_key; + struct amount_msat *msat; + u32 *cltv; + u64 *partid, *groupid; + struct sha256 *local_invreq_id; + struct secret shared_secret; + struct onionpacket *op; + struct onion_payload *payload; + struct route_step *rs; + u64 failtlvtype; + size_t failtlvpos; + struct channel *next; + struct command_result *ret; + const u8 *failmsg; + struct htlc_out *hout; + + if (!param_check(cmd, buffer, params, + p_req("onion", param_bin_from_hex, &onion), + p_req("payment_hash", param_sha256, &payment_hash), + p_req("amount_msat", param_msat, &msat), + p_req("cltv_expiry", param_u32, &cltv), + p_req("partid", param_u64_nonzero, &partid), + p_req("groupid", param_u64, &groupid), + p_opt("blinding", param_pubkey, &blinding), + p_opt("label", param_escaped_string, &label), + p_opt("invstring", param_invstring, &invstring), + p_opt("localinvreqid", param_sha256, &local_invreq_id), + NULL)) + return command_param_failed(); + + /* Safety check: reconcile this with previous attempts, check + * partid/groupid uniqueness: we don't know amount or total. */ + ret = check_progress(cmd->ld, cmd, payment_hash, AMOUNT_MSAT(0), + AMOUNT_MSAT(0), + *partid, *groupid, NULL); + if (ret) + return ret; + + /* This checks we're not trying to pay our a locally-generated + * invoice_request more than once. */ + ret = check_invoice_request_usage(cmd, local_invreq_id); + if (ret) + return ret; + + if (tal_bytelen(onion) != TOTAL_PACKET_SIZE(ROUTING_INFO_SIZE)) { + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "onion must be %u bytes long", + TOTAL_PACKET_SIZE(ROUTING_INFO_SIZE)); + } + + op = parse_onionpacket(tmpctx, onion, tal_bytelen(onion), + &failcode); + if (!op) { + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Could not parse onion: %s", + onion_wire_name(failcode)); + } + + if (!ecdh_maybe_blinding(&op->ephemeralkey, + blinding, + &shared_secret)) { + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Could not tweak ephemeral key"); + } + + rs = process_onionpacket(tmpctx, op, &shared_secret, + payment_hash->u.u8, + sizeof(payment_hash->u.u8)); + if (!rs) { + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Could not process onion"); + } + + payload = onion_decode(tmpctx, rs, blinding, + cmd->ld->accept_extra_tlv_types, + *msat, *cltv, + &failtlvtype, + &failtlvpos); + if (!payload) { + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Onion decode for %s failed at type %"PRIu64" offset %zu", + tal_hex(tmpctx, rs->raw_payload), + failtlvtype, failtlvpos); + } + + if (payload->final) { + struct selfpay *selfpay; + + if (command_check_only(cmd)) + return command_check_done(cmd); + + selfpay = tal(cmd, struct selfpay); + selfpay->cmd = cmd; + selfpay->shared_secret = shared_secret; + selfpay->partid = *partid; + selfpay->groupid = *groupid; + selfpay->payment_hash = *payment_hash; + + /* We actually *do* know msat delivered and total msat, but + * then check_progress will complain on the next part, because + * we don't know it then, so leave them 0 */ + register_payment_and_waiter(cmd, + payment_hash, + *partid, *groupid, + AMOUNT_MSAT(0), *msat, AMOUNT_MSAT(0), + label, invstring, local_invreq_id, + &shared_secret); + + /* Mark it pending now, though htlc_set_add might + * not resolve immediately */ + fixme_ignore(command_still_pending(cmd)); + htlc_set_add(cmd->ld, cmd->ld->log, *msat, *payload->total_msat, + payment_hash, payload->payment_secret, + selfpay_mpp_fail, selfpay_mpp_succeeded, + selfpay); + return command_its_complicated("htlc_set_add may have immediately succeeded or failed"); + } + + /* If they use scid, we use exactly the channel they tell us to here! */ + if (payload->forward_channel) { + next = any_channel_by_scid(cmd->ld, + *payload->forward_channel, + false); + if (!next) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Unknown scid %s", + fmt_short_channel_id(tmpctx, + *payload->forward_channel)); + + if (!channel_state_can_add_htlc(next->state)) { + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "channel %s in state %s", + fmt_short_channel_id(tmpctx, + *payload->forward_channel), + channel_state_str(next->state)); + } + } else { + struct node_id nid; + struct peer *next_peer; + + node_id_from_pubkey(&nid, payload->forward_node_id); + next_peer = peer_by_id(cmd->ld, &nid); + if (!next_peer) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Unknown peer %s", + fmt_node_id(tmpctx, &nid)); + + next = best_channel(cmd->ld, next_peer, *msat, NULL); + if (!next) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "No available channel with peer %s", + fmt_node_id(tmpctx, &nid)); + } + + if (amount_msat_greater(*msat, next->htlc_maximum_msat) + || amount_msat_less(*msat, next->htlc_minimum_msat)) { + /* Are we in old-range grace-period? */ + if (!time_before(time_now(), next->old_feerate_timeout) + || amount_msat_less(*msat, next->old_htlc_minimum_msat) + || amount_msat_greater(*msat, next->old_htlc_maximum_msat)) { + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Amount %s not in htlc min/max range %s-%s", + fmt_amount_msat(tmpctx, *msat), + fmt_amount_msat(tmpctx, next->htlc_minimum_msat), + fmt_amount_msat(tmpctx, next->htlc_maximum_msat)); + } + log_info(next->log, + "Allowing payment using older htlc_minimum/maximum_msat"); + } + + /* BOLT #2: + * + * An offering node: + * - MUST estimate a timeout deadline for each HTLC it offers. + * - MUST NOT offer an HTLC with a timeout deadline before its + * `cltv_expiry`. + */ + /* In our case, G = 1, so we need to expire it one after it's expiration. + * But never offer an expired HTLC; that's dumb. */ + if (get_block_height(cmd->ld->topology) >= *cltv) { + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Expiry cltv %u too close to current %u", + *cltv, + get_block_height(ld->topology)); + } + + /* BOLT #4: + * + * - if the `cltv_expiry` is more than `max_htlc_cltv` in the future: + * - return an `expiry_too_far` error. + */ + if (get_block_height(ld->topology) + + ld->config.max_htlc_cltv < *cltv) { + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Expiry cltv %u too far from current %u + max %u", + *cltv, + get_block_height(ld->topology), + ld->config.max_htlc_cltv); + } + + /* We could have blinding from cmdline or from inside onion. */ + if (payload->path_key) { + struct sha256 sha; + blinding_hash_e_and_ss(payload->path_key, + &payload->blinding_ss, + &sha); + next_path_key = tal(tmpctx, struct pubkey); + blinding_next_path_key(payload->path_key, &sha, + next_path_key); + } else + next_path_key = NULL; + + if (command_check_only(cmd)) + return command_check_done(cmd); + + register_payment_and_waiter(cmd, + payment_hash, + *partid, *groupid, + AMOUNT_MSAT(0), *msat, AMOUNT_MSAT(0), + label, invstring, local_invreq_id, + &shared_secret); + + failmsg = send_htlc_out(tmpctx, next, *msat, + /* We set final_msat to the same, so fees == 0 + * (in fact, we don't know!) */ + *cltv, *msat, + payment_hash, + next_path_key, *partid, *groupid, + serialize_onionpacket(tmpctx, rs->next), + NULL, &hout); + if (failmsg) { + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Could not send to first peer: %s", + onion_wire_name(fromwire_peektype(failmsg))); + } + return command_still_pending(cmd); +} + +static const struct json_command injectpaymentonion_command = { + "injectpaymentonion", + json_injectpaymentonion, +}; +AUTODATA(json_command, &injectpaymentonion_command); + + static u64 sendpay_index_inc(struct lightningd *ld, const struct sha256 *payment_hash, u64 partid, diff --git a/lightningd/pay.h b/lightningd/pay.h index bf516df87b0f..5f7faa75221d 100644 --- a/lightningd/pay.h +++ b/lightningd/pay.h @@ -18,8 +18,13 @@ void payment_succeeded(struct lightningd *ld, u64 partid, u64 groupid, const struct preimage *rval); -/* hout->failmsg or hout->failonion must be set. */ -void payment_failed(struct lightningd *ld, const struct htlc_out *hout, +/* failmsg or failonion must be set. */ +void payment_failed(struct lightningd *ld, + struct logger *log, + const struct sha256 *payment_hash, + u64 partid, u64 groupid, + const struct onionreply *failonion, + const u8 *failmsg, const char *localfail); /* This json will be also used in 'sendpay_success' notifictaion. */ diff --git a/lightningd/peer_htlcs.c b/lightningd/peer_htlcs.c index 955dffa94fd0..dff7e495ebc2 100644 --- a/lightningd/peer_htlcs.c +++ b/lightningd/peer_htlcs.c @@ -275,15 +275,12 @@ void local_fail_in_htlc(struct htlc_in *hin, const u8 *failmsg TAKES) } /* Helper to create (common) WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS */ -const u8 *failmsg_incorrect_or_unknown_(const tal_t *ctx, - struct lightningd *ld, - const struct htlc_in *hin, - const char *file, int line) +const u8 *failmsg_incorrect_or_unknown(const tal_t *ctx, + struct lightningd *ld, + struct amount_msat msat) { - log_debug(ld->log, "WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS: %s:%u", - file, line); return towire_incorrect_or_unknown_payment_details( - ctx, hin->msat, + ctx, msat, get_block_height(ld->topology)); } @@ -294,7 +291,14 @@ static void fail_out_htlc(struct htlc_out *hout, const char *localfail) assert(hout->failmsg || hout->failonion); if (hout->am_origin) { - payment_failed(hout->key.channel->peer->ld, hout, localfail); + payment_failed(hout->key.channel->peer->ld, + hout->key.channel->log, + &hout->payment_hash, + hout->partid, + hout->groupid, + hout->failonion, + hout->failmsg, + localfail); } else if (hout->in) { const struct onionreply *failonion; @@ -410,6 +414,16 @@ void fulfill_htlc(struct htlc_in *hin, const struct preimage *preimage) subd_send_msg(channel->owner, take(msg)); } +/* HTLC-specific wrappers */ +static void htlc_set_fulfill_htlc(struct htlc_in *hin, + const struct preimage *preimage) +{ + /* mark that we filled -- needed for tagging coin mvt */ + hin->we_filled = tal(hin, bool); + *hin->we_filled = true; + fulfill_htlc(hin, preimage); +} + static void handle_localpay(struct htlc_in *hin, struct amount_msat amt_to_forward, u32 outgoing_cltv_value, @@ -477,7 +491,7 @@ static void handle_localpay(struct htlc_in *hin, hin->cltv_expiry, get_block_height(ld->topology), ld->config.cltv_final); - failmsg = failmsg_incorrect_or_unknown(NULL, ld, hin); + failmsg = failmsg_incorrect_or_unknown(NULL, ld, hin->msat); goto fail; } @@ -502,7 +516,13 @@ static void handle_localpay(struct htlc_in *hin, goto fail; } - htlc_set_add(ld, hin, total_msat, payment_secret); + htlc_set_add(ld, hin->key.channel->log, + hin->msat, total_msat, + &hin->payment_hash, + payment_secret, + local_fail_in_htlc, + htlc_set_fulfill_htlc, + hin); return; fail: @@ -585,8 +605,14 @@ static void rcvd_htlc_reply(struct subd *subd, const u8 *msg, const int *fds UNU char *localfail = tal_fmt(msg, "%s: %s", onion_wire_name(fromwire_peektype(failmsg)), failurestr); - payment_failed(ld, hout, localfail); - + payment_failed(ld, + hout->key.channel->log, + &hout->payment_hash, + hout->partid, + hout->groupid, + hout->failonion, + hout->failmsg, + localfail); } else if (hout->in) { struct onionreply *failonion; struct short_channel_id scid; @@ -713,13 +739,13 @@ const u8 *send_htlc_out(const tal_t *ctx, /* What's the best channel to this peer? * If @hint is set, channel must match that one. */ -static struct channel *best_channel(struct lightningd *ld, - const struct peer *next_peer, - struct amount_msat amt_to_forward, - struct channel *hint) +struct channel *best_channel(struct lightningd *ld, + const struct peer *next_peer, + struct amount_msat amt_to_forward, + const struct channel *hint) { struct amount_msat best_spendable = AMOUNT_MSAT(0); - struct channel *channel, *best = hint; + struct channel *channel, *best = cast_const(struct channel *, hint); /* Seek channel with largest spendable! */ list_for_each(&next_peer->channels, channel, list) { @@ -1176,9 +1202,9 @@ htlc_accepted_hook_final(struct htlc_accepted_hook_payload *request STEALS) } /* Apply tweak to ephemeral key if path_key is non-NULL, then do ECDH */ -static bool ecdh_maybe_blinding(const struct pubkey *ephemeral_key, - const struct pubkey *path_key, - struct secret *ss) +bool ecdh_maybe_blinding(const struct pubkey *ephemeral_key, + const struct pubkey *path_key, + struct secret *ss) { struct pubkey point = *ephemeral_key; @@ -1770,7 +1796,14 @@ void onchain_failed_our_htlc(const struct channel *channel, char *localfail = tal_fmt(channel, "%s: %s", onion_wire_name(WIRE_PERMANENT_CHANNEL_FAILURE), why); - payment_failed(ld, hout, localfail); + payment_failed(ld, + hout->key.channel->log, + &hout->payment_hash, + hout->partid, + hout->groupid, + hout->failonion, + hout->failmsg, + localfail); tal_free(localfail); } else if (hout->in) { struct short_channel_id scid = channel_scid_or_local_alias(channel); diff --git a/lightningd/peer_htlcs.h b/lightningd/peer_htlcs.h index f67ff0ed530c..9b9e67255b74 100644 --- a/lightningd/peer_htlcs.h +++ b/lightningd/peer_htlcs.h @@ -54,6 +54,18 @@ void fixup_htlcs_out(struct lightningd *ld); void htlcs_resubmit(struct lightningd *ld, struct htlc_in_map *unconnected_htlcs_in STEALS); +/* Apply tweak to ephemeral key if path_key is non-NULL, then do ECDH */ +bool ecdh_maybe_blinding(const struct pubkey *ephemeral_key, + const struct pubkey *path_key, + struct secret *ss); + +/* Select best (highest capacity) to peer. If hint is set, must match that + * feerate */ +struct channel *best_channel(struct lightningd *ld, + const struct peer *next_peer, + struct amount_msat amt_to_forward, + const struct channel *hint); + /* For HTLCs which terminate here, invoice payment calls one of these. */ void fulfill_htlc(struct htlc_in *hin, const struct preimage *preimage); void local_fail_in_htlc(struct htlc_in *hin, const u8 *failmsg TAKES); @@ -62,11 +74,7 @@ void local_fail_in_htlc_needs_update(struct htlc_in *hin, const struct short_channel_id *failmsg_scid); /* Helper to create (common) WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS */ -#define failmsg_incorrect_or_unknown(ctx, ld, hin) \ - failmsg_incorrect_or_unknown_((ctx), (ld), (hin), __FILE__, __LINE__) - -const u8 *failmsg_incorrect_or_unknown_(const tal_t *ctx, - struct lightningd *ld, - const struct htlc_in *hin, - const char *file, int line); +const u8 *failmsg_incorrect_or_unknown(const tal_t *ctx, + struct lightningd *ld, + struct amount_msat msat); #endif /* LIGHTNING_LIGHTNINGD_PEER_HTLCS_H */ diff --git a/lightningd/test/run-invoice-select-inchan.c b/lightningd/test/run-invoice-select-inchan.c index 4d2b0832b1c2..1a0b4bce0a94 100644 --- a/lightningd/test/run-invoice-select-inchan.c +++ b/lightningd/test/run-invoice-select-inchan.c @@ -251,12 +251,6 @@ u8 *encrypt_tlv_encrypted_data(const tal_t *ctx UNNEEDED, struct pubkey *node_alias) { fprintf(stderr, "encrypt_tlv_encrypted_data called!\n"); abort(); } -/* Generated stub for failmsg_incorrect_or_unknown_ */ -const u8 *failmsg_incorrect_or_unknown_(const tal_t *ctx UNNEEDED, - struct lightningd *ld UNNEEDED, - const struct htlc_in *hin UNNEEDED, - const char *file UNNEEDED, int line UNNEEDED) -{ fprintf(stderr, "failmsg_incorrect_or_unknown_ called!\n"); abort(); } /* Generated stub for fatal */ void fatal(const char *fmt UNNEEDED, ...) { fprintf(stderr, "fatal called!\n"); abort(); } @@ -375,9 +369,10 @@ bool htlc_is_trimmed(enum side htlc_owner UNNEEDED, /* Generated stub for htlc_max_possible_send */ struct amount_msat htlc_max_possible_send(const struct channel *channel UNNEEDED) { fprintf(stderr, "htlc_max_possible_send called!\n"); abort(); } -/* Generated stub for htlc_set_fail */ -void htlc_set_fail(struct htlc_set *set UNNEEDED, const u8 *failmsg TAKES UNNEEDED) -{ fprintf(stderr, "htlc_set_fail called!\n"); abort(); } +/* Generated stub for htlc_set_fail_ */ +void htlc_set_fail_(struct htlc_set *set UNNEEDED, const u8 *failmsg TAKES UNNEEDED, + const char *file UNNEEDED, int line UNNEEDED) +{ fprintf(stderr, "htlc_set_fail_ called!\n"); abort(); } /* Generated stub for htlc_set_fulfill */ void htlc_set_fulfill(struct htlc_set *set UNNEEDED, const struct preimage *preimage UNNEEDED) { fprintf(stderr, "htlc_set_fulfill called!\n"); abort(); } @@ -699,6 +694,9 @@ struct jsonrpc_request *jsonrpc_request_start_( void kill_uncommitted_channel(struct uncommitted_channel *uc UNNEEDED, const char *why UNNEEDED) { fprintf(stderr, "kill_uncommitted_channel called!\n"); abort(); } +/* Generated stub for local_fail_in_htlc */ +void local_fail_in_htlc(struct htlc_in *hin UNNEEDED, const u8 *failmsg TAKES UNNEEDED) +{ fprintf(stderr, "local_fail_in_htlc called!\n"); abort(); } /* Generated stub for lockin_complete */ void lockin_complete(struct channel *channel UNNEEDED, enum channel_state expected_state UNNEEDED) diff --git a/tests/test_pay.py b/tests/test_pay.py index 2035285fe4ae..540ee1ea78f0 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -1,13 +1,14 @@ from fixtures import * # noqa: F401,F403 from fixtures import TEST_NETWORK +from hashlib import sha256 from pathlib import Path -from io import BytesIO from pyln.client import RpcError, Millisatoshi from pyln.proto.onion import TlvPayload from pyln.testing.utils import EXPERIMENTAL_DUAL_FUND, FUNDAMOUNT, scid_to_int from utils import ( wait_for, only_one, sync_blockheight, TIMEOUT, - mine_funding_to_announce, first_scid + mine_funding_to_announce, first_scid, serialize_payload_tlv, serialize_payload_final_tlv, + tu64_encode ) import copy import os @@ -15,7 +16,6 @@ import random import re import string -import struct import subprocess import time import unittest @@ -2980,80 +2980,19 @@ def test_sendonion_rpc(node_factory): first_hop = route[0] blockheight = l1.rpc.getinfo()['blockheight'] - def truncate_encode(i: int): - """Encode a tu64 (or tu32 etc) value""" - ret = struct.pack("!Q", i) - while ret.startswith(b'\0'): - ret = ret[1:] - return ret - - def serialize_payload_tlv(n): - block, tx, out = n['channel'].split('x') - - payload = TlvPayload() - # BOLT #4: - # 1. type: 2 (`amt_to_forward`) - # 2. data: - # * [`tu64`:`amt_to_forward`] - b = BytesIO() - b.write(truncate_encode(int(n['amount_msat']))) - payload.add_field(2, b.getvalue()) - # BOLT #4: - # 1. type: 4 (`outgoing_cltv_value`) - # 2. data: - # * [`tu32`:`outgoing_cltv_value`] - b = BytesIO() - b.write(truncate_encode(blockheight + n['delay'])) - payload.add_field(4, b.getvalue()) - # BOLT #4: - # 1. type: 6 (`short_channel_id`) - # 2. data: - # * [`short_channel_id`:`short_channel_id`] - b = BytesIO() - b.write(struct.pack("!Q", int(block) << 40 | int(tx) << 16 | int(out))) - payload.add_field(6, b.getvalue()) - return payload.to_bytes().hex() - - def serialize_payload_final_tlv(n, payment_secret: str): - payload = TlvPayload() - # BOLT #4: - # 1. type: 2 (`amt_to_forward`) - # 2. data: - # * [`tu64`:`amt_to_forward`] - b = BytesIO() - b.write(truncate_encode(int(n['amount_msat']))) - payload.add_field(2, b.getvalue()) - # BOLT #4: - # 1. type: 4 (`outgoing_cltv_value`) - # 2. data: - # * [`tu32`:`outgoing_cltv_value`] - b = BytesIO() - b.write(truncate_encode(blockheight + n['delay'])) - payload.add_field(4, b.getvalue()) - # BOLT #4: - # 1. type: 8 (`payment_data`) - # 2. data: - # * [`32*byte`:`payment_secret`] - # * [`tu64`:`total_msat`] - b = BytesIO() - b.write(bytes.fromhex(payment_secret)) - b.write(truncate_encode(int(n['amount_msat']))) - payload.add_field(8, b.getvalue()) - return payload.to_bytes().hex() - # Need to shift the parameters by one hop hops = [] for h, n in zip(route[:-1], route[1:]): # We tell the node h about the parameters to use for n (a.k.a. h + 1) hops.append({ "pubkey": h['id'], - "payload": serialize_payload_tlv(n) + "payload": serialize_payload_tlv(n['amount_msat'], n['delay'], n['channel'], blockheight).hex() }) # The last hop has a special payload: hops.append({ "pubkey": route[-1]['id'], - "payload": serialize_payload_final_tlv(route[-1], inv['payment_secret']) + "payload": serialize_payload_final_tlv(route[-1]['amount_msat'], route[-1]['delay'], route[-1]['amount_msat'], blockheight, inv['payment_secret']).hex() }) onion = l1.rpc.createonion(hops=hops, assocdata=inv['payment_hash']) @@ -6128,3 +6067,516 @@ def test_pay_remember_hint(node_factory): # We should not have touched fw1, and should succeed after a single call p = sender.rpc.pay(inv) assert(p['parts'] == 1) + + +def test_injectpaymentonion_simple(node_factory, executor): + l1, l2 = node_factory.line_graph(2) + + blockheight = l1.rpc.getinfo()['blockheight'] + inv1 = l2.rpc.invoice(1000, "test_injectpaymentonion1", "test_injectpaymentonion1") + + # First hop for injectpaymentonion is self. + hops = [{'pubkey': l1.info['id'], + 'payload': serialize_payload_tlv(1000, 18 + 6, first_scid(l1, l2), blockheight).hex()}, + {'pubkey': l2.info['id'], + 'payload': serialize_payload_final_tlv(1000, 18, 1000, blockheight, inv1['payment_secret']).hex()}] + onion = l1.rpc.createonion(hops=hops, assocdata=inv1['payment_hash']) + + ret = l1.rpc.injectpaymentonion(onion=onion['onion'], + payment_hash=inv1['payment_hash'], + amount_msat=1000, + cltv_expiry=blockheight + 18 + 6, + partid=1, + groupid=0) + assert ret['completed_at'] >= ret['created_at'] + assert sha256(bytes.fromhex(ret['payment_preimage'])).hexdigest() == inv1['payment_hash'] + assert ret == {'payment_preimage': ret['payment_preimage'], + 'created_index': 1, + 'completed_at': ret['completed_at'], + 'created_at': ret['created_at']} + assert only_one(l2.rpc.listinvoices("test_injectpaymentonion1")['invoices'])['status'] == 'paid' + lsp = only_one(l1.rpc.listsendpays(inv1['bolt11'])['payments']) + assert lsp['groupid'] == 0 + assert lsp['partid'] == 1 + assert lsp['payment_hash'] == inv1['payment_hash'] + assert lsp['status'] == 'complete' + + +def test_injectpaymentonion_mpp(node_factory, executor): + l1, l2 = node_factory.line_graph(2) + + blockheight = l1.rpc.getinfo()['blockheight'] + inv2 = l2.rpc.invoice(3000, "test_injectpaymentonion2", "test_injectpaymentonion2") + + # First hop for injectpaymentonion is self. + hops1 = [{'pubkey': l1.info['id'], + 'payload': serialize_payload_tlv(1000, 18 + 6, first_scid(l1, l2), blockheight).hex()}, + {'pubkey': l2.info['id'], + 'payload': serialize_payload_final_tlv(1000, 18, 3000, blockheight, inv2['payment_secret']).hex()}] + onion1 = l1.rpc.createonion(hops=hops1, assocdata=inv2['payment_hash']) + hops2 = [{'pubkey': l1.info['id'], + 'payload': serialize_payload_tlv(2000, 18 + 6, first_scid(l1, l2), blockheight).hex()}, + {'pubkey': l2.info['id'], + 'payload': serialize_payload_final_tlv(2000, 18, 3000, blockheight, inv2['payment_secret']).hex()}] + onion2 = l1.rpc.createonion(hops=hops2, assocdata=inv2['payment_hash']) + + fut1 = executor.submit(l1.rpc.injectpaymentonion, + onion1['onion'], + inv2['payment_hash'], + 1000, + blockheight + 18 + 6, + 1, + 0) + fut2 = executor.submit(l1.rpc.injectpaymentonion, + onion2['onion'], + inv2['payment_hash'], + 2000, + blockheight + 18 + 6, + 2, + 0) + + # Now both should complete. + ret = fut1.result(TIMEOUT) + assert sha256(bytes.fromhex(ret['payment_preimage'])).hexdigest() == inv2['payment_hash'] + ret = fut2.result(TIMEOUT) + assert sha256(bytes.fromhex(ret['payment_preimage'])).hexdigest() == inv2['payment_hash'] + + assert only_one(l2.rpc.listinvoices("test_injectpaymentonion2")['invoices'])['status'] == 'paid' + lsps = l1.rpc.listsendpays(inv2['bolt11'])['payments'] + for lsp in lsps: + assert lsp['groupid'] == 0 + assert lsp['partid'] == 1 or lsp['partid'] == 2 + assert lsp['payment_hash'] == inv2['payment_hash'] + assert lsp['status'] == 'complete' + assert len(lsps) == 2 + + +def test_injectpaymentonion_3hop(node_factory, executor): + l1, l2, l3 = node_factory.line_graph(3, wait_for_announce=True) + + blockheight = l1.rpc.getinfo()['blockheight'] + inv3 = l3.rpc.invoice(1000, "test_injectpaymentonion3", "test_injectpaymentonion3") + + # First hop for injectpaymentonion is self. + hops = [{'pubkey': l1.info['id'], + 'payload': serialize_payload_tlv(1001, 18 + 6 + 6, first_scid(l1, l2), blockheight).hex()}, + {'pubkey': l2.info['id'], + 'payload': serialize_payload_tlv(1000, 18 + 6, first_scid(l3, l2), blockheight).hex()}, + {'pubkey': l3.info['id'], + 'payload': serialize_payload_final_tlv(1000, 18, 1000, blockheight, inv3['payment_secret']).hex()}] + onion = l1.rpc.createonion(hops=hops, assocdata=inv3['payment_hash']) + + ret = l1.rpc.injectpaymentonion(onion=onion['onion'], + payment_hash=inv3['payment_hash'], + amount_msat=1001, + cltv_expiry=blockheight + 18 + 6 + 6, + partid=1, + groupid=0) + assert sha256(bytes.fromhex(ret['payment_preimage'])).hexdigest() == inv3['payment_hash'] + assert only_one(l3.rpc.listinvoices("test_injectpaymentonion3")['invoices'])['status'] == 'paid' + lsp = only_one(l1.rpc.listsendpays(inv3['bolt11'])['payments']) + assert lsp['groupid'] == 0 + assert lsp['partid'] == 1 + assert lsp['payment_hash'] == inv3['payment_hash'] + assert lsp['status'] == 'complete' + + +def test_injectpaymentonion_selfpay(node_factory, executor): + l1, l2 = node_factory.line_graph(2, opts={'experimental-offers': None}) + + blockheight = l1.rpc.getinfo()['blockheight'] + + # Test simple self-pay. + inv4 = l1.rpc.invoice(1000, "test_injectpaymentonion4", "test_injectpaymentonion4") + + # First hop for injectpaymentonion is self. + hops = [{'pubkey': l1.info['id'], + 'payload': serialize_payload_final_tlv(1000, 18, 1000, blockheight, inv4['payment_secret']).hex()}] + onion = l1.rpc.createonion(hops=hops, assocdata=inv4['payment_hash']) + + ret = l1.rpc.injectpaymentonion(onion=onion['onion'], + payment_hash=inv4['payment_hash'], + amount_msat=1000, + cltv_expiry=blockheight + 18, + partid=1, + groupid=0) + assert sha256(bytes.fromhex(ret['payment_preimage'])).hexdigest() == inv4['payment_hash'] + assert only_one(l1.rpc.listinvoices("test_injectpaymentonion4")['invoices'])['status'] == 'paid' + lsp = only_one(l1.rpc.listsendpays(inv4['bolt11'])['payments']) + assert lsp['groupid'] == 0 + assert lsp['partid'] == 1 + assert lsp['payment_hash'] == inv4['payment_hash'] + assert lsp['status'] == 'complete' + + # Test self-pay with MPP. + inv5 = l1.rpc.invoice(1000, "test_injectpaymentonion5", "test_injectpaymentonion5") + + # First hop for injectpaymentonion is self. + hops1 = [{'pubkey': l1.info['id'], + 'payload': serialize_payload_final_tlv(333, 18, 1000, blockheight, inv5['payment_secret']).hex()}] + onion1 = l1.rpc.createonion(hops=hops1, assocdata=inv5['payment_hash']) + hops2 = [{'pubkey': l1.info['id'], + 'payload': serialize_payload_final_tlv(666, 18, 1000, blockheight, inv5['payment_secret']).hex()}] + onion2 = l1.rpc.createonion(hops=hops2, assocdata=inv5['payment_hash']) + + fut1 = executor.submit(l1.rpc.injectpaymentonion, + onion1['onion'], + inv5['payment_hash'], + 333, + blockheight + 18, + 1, + 0) + fut2 = executor.submit(l1.rpc.injectpaymentonion, + onion2['onion'], + inv5['payment_hash'], + 667, + blockheight + 18, + 2, + 0) + # Now both should complete. + ret = fut1.result(TIMEOUT) + assert sha256(bytes.fromhex(ret['payment_preimage'])).hexdigest() == inv5['payment_hash'] + + ret = fut2.result(TIMEOUT) + assert sha256(bytes.fromhex(ret['payment_preimage'])).hexdigest() == inv5['payment_hash'] + + assert only_one(l1.rpc.listinvoices("test_injectpaymentonion5")['invoices'])['status'] == 'paid' + lsps = l1.rpc.listsendpays(inv5['bolt11'])['payments'] + for lsp in lsps: + assert lsp['groupid'] == 0 + assert lsp['partid'] == 1 or lsp['partid'] == 2 + assert lsp['payment_hash'] == inv5['payment_hash'] + assert lsp['status'] == 'complete' + assert len(lsps) == 2 + + # Check listpays gives a reasonable result! + pays = only_one(l1.rpc.listpays(inv5['bolt11'])['pays']) + # Don't know these values + del pays['created_at'] + del pays['completed_at'] + del pays['preimage'] + assert pays == {'bolt11': inv5['bolt11'], + 'payment_hash': inv5['payment_hash'], + 'status': "complete", + 'amount_sent_msat': 1000, + 'number_of_parts': 2} + + # Test self-pay with MPP from non-selfpay. + inv6 = l2.rpc.invoice(3000, "test_injectpaymentonion6", "test_injectpaymentonion6") + + # First hop for injectpaymentonion is self. + hops1 = [{'pubkey': l1.info['id'], + 'payload': serialize_payload_tlv(1000, 18 + 6, first_scid(l1, l2), blockheight).hex()}, + {'pubkey': l2.info['id'], + 'payload': serialize_payload_final_tlv(1000, 18, 3000, blockheight, inv6['payment_secret']).hex()}] + onion1 = l1.rpc.createonion(hops=hops1, assocdata=inv6['payment_hash']) + hops2 = [{'pubkey': l2.info['id'], + 'payload': serialize_payload_final_tlv(2000, 18, 3000, blockheight, inv6['payment_secret']).hex()}] + onion2 = l1.rpc.createonion(hops=hops2, assocdata=inv6['payment_hash']) + + fut1 = executor.submit(l1.rpc.injectpaymentonion, + onion1['onion'], + inv6['payment_hash'], + 1000, + blockheight + 18 + 6, + 1, + 0) + fut2 = executor.submit(l2.rpc.injectpaymentonion, + onion2['onion'], + inv6['payment_hash'], + 2000, + blockheight + 18, + 2, + 1) + + # Now both should complete. + ret = fut1.result(TIMEOUT) + assert sha256(bytes.fromhex(ret['payment_preimage'])).hexdigest() == inv6['payment_hash'] + + ret = fut2.result(TIMEOUT) + assert sha256(bytes.fromhex(ret['payment_preimage'])).hexdigest() == inv6['payment_hash'] + + assert only_one(l2.rpc.listinvoices("test_injectpaymentonion6")['invoices'])['status'] == 'paid' + lsp = only_one(l1.rpc.listsendpays(inv6['bolt11'])['payments']) + assert lsp['groupid'] == 0 + assert lsp['partid'] == 1 + assert lsp['payment_hash'] == inv6['payment_hash'] + assert lsp['status'] == 'complete' + lsp = only_one(l2.rpc.listsendpays(inv6['bolt11'])['payments']) + assert lsp['groupid'] == 1 + assert lsp['partid'] == 2 + assert lsp['payment_hash'] == inv6['payment_hash'] + assert lsp['status'] == 'complete' + + # Test bolt12 self-pay. + offer = l1.rpc.offer('any') + inv10 = l1.rpc.fetchinvoice(offer['bolt12'], '1000msat') + decoded = l1.rpc.decode(inv10['invoice']) + + final_tlvs = TlvPayload() + final_tlvs.add_field(2, tu64_encode(1000)) + final_tlvs.add_field(4, tu64_encode(blockheight + 18)) + final_tlvs.add_field(10, bytes.fromhex(decoded['invoice_paths'][0]['path'][0]['encrypted_recipient_data'])) + final_tlvs.add_field(12, bytes.fromhex(decoded['invoice_paths'][0]['first_path_key'])) + final_tlvs.add_field(18, tu64_encode(1000)) + + hops = [{'pubkey': l1.info['id'], + 'payload': final_tlvs.to_bytes().hex()}] + onion = l1.rpc.createonion(hops=hops, assocdata=decoded['invoice_payment_hash']) + + ret = l1.rpc.injectpaymentonion(onion=onion['onion'], + payment_hash=decoded['invoice_payment_hash'], + amount_msat=1000, + cltv_expiry=blockheight + 18, + partid=1, + groupid=0) + assert sha256(bytes.fromhex(ret['payment_preimage'])).hexdigest() == decoded['invoice_payment_hash'] + # The label for the invoice is deterministic. + label = f"{decoded['offer_id']}-{decoded['invreq_payer_id']}-0" + assert only_one(l1.rpc.listinvoices(label)['invoices'])['status'] == 'paid' + lsp = only_one(l1.rpc.listsendpays(inv4['bolt11'])['payments']) + assert lsp['groupid'] == 0 + assert lsp['partid'] == 1 + assert lsp['payment_hash'] == inv4['payment_hash'] + assert lsp['status'] == 'complete' + + +def test_injectpaymentonion_blindedpath(node_factory, executor): + l1, l2 = node_factory.line_graph(2, + wait_for_announce=True, + opts={'experimental-offers': None}) + blockheight = l1.rpc.getinfo()['blockheight'] + + # Test bolt12, with stub blinded path. + offer = l2.rpc.offer('any') + inv7 = l1.rpc.fetchinvoice(offer['bolt12'], '1000msat') + + decoded = l1.rpc.decode(inv7['invoice']) + assert len(decoded['invoice_paths']) == 1 + path_key = decoded['invoice_paths'][0]['first_path_key'] + assert decoded['invoice_paths'][0]['first_node_id'] == l2.info['id'] + path = decoded['invoice_paths'][0]['path'] + assert len(path) == 1 + + # Manually encode the onion payload to include blinded info + # BOLT #4: + # - For every node inside a blinded route: + # - MUST include the `encrypted_recipient_data` provided by the recipient + # - For the first node in the blinded route: + # - MUST include the `path_key` provided by the recipient in `current_path_key` + # - If it is the final node: + # - MUST include `amt_to_forward`, `outgoing_cltv_value` and `total_amount_msat`. + # - The value set for `outgoing_cltv_value`: + # - MUST use the current block height as a baseline value. + # - if a [random offset](07-routing-gossip.md#recommendations-for-routing) was added to improve privacy: + # - SHOULD add the offset to the baseline value. + # - MUST NOT include any other tlv field. + final_tlvs = TlvPayload() + + # BOLT #4: + # 1. type: 2 (`amt_to_forward`) + # 2. data: + # * [`tu64`:`amt_to_forward`] + # 1. type: 4 (`outgoing_cltv_value`) + # 2. data: + # * [`tu32`:`outgoing_cltv_value`] + # ... + # 1. type: 10 (`encrypted_recipient_data`) + # 2. data: + # * [`...*byte`:`encrypted_recipient_data`] + # 1. type: 12 (`current_path_key`) + # 2. data: + # * [`point`:`path_key`] + # ... + # 1. type: 18 (`total_amount_msat`) + # 2. data: + # * [`tu64`:`total_msat`] + final_tlvs.add_field(2, tu64_encode(1000)) + final_tlvs.add_field(4, tu64_encode(blockheight + 18)) + final_tlvs.add_field(10, bytes.fromhex(path[0]['encrypted_recipient_data'])) + final_tlvs.add_field(12, bytes.fromhex(path_key)) + final_tlvs.add_field(18, tu64_encode(1000)) + + hops = [{'pubkey': l1.info['id'], + 'payload': serialize_payload_tlv(1000, 18 + 6, first_scid(l1, l2), blockheight).hex()}, + {'pubkey': l2.info['id'], + 'payload': final_tlvs.to_bytes().hex()}] + onion = l1.rpc.createonion(hops=hops, assocdata=decoded['invoice_payment_hash']) + + ret = l1.rpc.injectpaymentonion(onion=onion['onion'], + payment_hash=decoded['invoice_payment_hash'], + amount_msat=1000, + cltv_expiry=blockheight + 18 + 6, + partid=1, + groupid=0) + assert sha256(bytes.fromhex(ret['payment_preimage'])).hexdigest() == decoded['invoice_payment_hash'] + # The label for l2's invoice is deterministic. + label = f"{decoded['offer_id']}-{decoded['invreq_payer_id']}-0" + assert only_one(l2.rpc.listinvoices(label)['invoices'])['status'] == 'paid' + + lsp = only_one(l1.rpc.listsendpays(inv7['invoice'])['payments']) + assert lsp['groupid'] == 0 + assert lsp['partid'] == 1 + assert lsp['payment_hash'] == decoded['invoice_payment_hash'] + assert lsp['status'] == 'complete' + + # Now test bolt12 with real blinded path. + l4 = node_factory.get_node(options={'experimental-offers': None}) + # Private channel. + node_factory.join_nodes([l2, l4], announce_channels=False) + + # Make sure l4 knows about other nodes, so will add route hint. + wait_for(lambda: len(l4.rpc.listnodes()['nodes']) == 2) + offer = l4.rpc.offer('any') + inv8 = l1.rpc.fetchinvoice(offer['bolt12'], '1000msat') + + decoded = l1.rpc.decode(inv8['invoice']) + assert len(decoded['invoice_paths']) == 1 + path_key = decoded['invoice_paths'][0]['first_path_key'] + assert decoded['invoice_paths'][0]['first_node_id'] == l2.info['id'] + path = decoded['invoice_paths'][0]['path'] + assert len(path) == 2 + + mid_tlvs = TlvPayload() + mid_tlvs.add_field(10, bytes.fromhex(path[0]['encrypted_recipient_data'])) + mid_tlvs.add_field(12, bytes.fromhex(path_key)) + + final_tlvs = TlvPayload() + final_tlvs.add_field(2, tu64_encode(1000)) + final_tlvs.add_field(4, tu64_encode(blockheight + 18)) + final_tlvs.add_field(10, bytes.fromhex(path[1]['encrypted_recipient_data'])) + final_tlvs.add_field(18, tu64_encode(1000)) + + hops = [{'pubkey': l1.info['id'], + 'payload': serialize_payload_tlv(1001, 18 + 6 + 6, first_scid(l1, l2), blockheight).hex()}, + {'pubkey': l2.info['id'], + 'payload': mid_tlvs.to_bytes().hex()}, + {'pubkey': path[1]['blinded_node_id'], + 'payload': final_tlvs.to_bytes().hex()}] + onion = l1.rpc.createonion(hops=hops, assocdata=decoded['invoice_payment_hash']) + + ret = l1.rpc.injectpaymentonion(onion=onion['onion'], + payment_hash=decoded['invoice_payment_hash'], + amount_msat=1001, + cltv_expiry=blockheight + 18 + 6, + partid=1, + groupid=0) + assert sha256(bytes.fromhex(ret['payment_preimage'])).hexdigest() == decoded['invoice_payment_hash'] + # The label for l4's invoice is deterministic. + label = f"{decoded['offer_id']}-{decoded['invreq_payer_id']}-0" + assert only_one(l4.rpc.listinvoices(label)['invoices'])['status'] == 'paid' + lsp = only_one(l1.rpc.listsendpays(inv8['invoice'])['payments']) + assert lsp['groupid'] == 0 + assert lsp['partid'] == 1 + assert lsp['payment_hash'] == decoded['invoice_payment_hash'] + assert lsp['status'] == 'complete' + + # Finally, with blinded path which starts with us. + offer = l4.rpc.offer('any') + inv9 = l1.rpc.fetchinvoice(offer['bolt12'], '1000msat') + + decoded = l1.rpc.decode(inv9['invoice']) + assert len(decoded['invoice_paths']) == 1 + path_key = decoded['invoice_paths'][0]['first_path_key'] + assert decoded['invoice_paths'][0]['first_node_id'] == l2.info['id'] + path = decoded['invoice_paths'][0]['path'] + assert len(path) == 2 + + mid_tlvs = TlvPayload() + mid_tlvs.add_field(10, bytes.fromhex(path[0]['encrypted_recipient_data'])) + mid_tlvs.add_field(12, bytes.fromhex(path_key)) + + final_tlvs = TlvPayload() + final_tlvs.add_field(2, tu64_encode(1000)) + final_tlvs.add_field(4, tu64_encode(blockheight + 18)) + final_tlvs.add_field(10, bytes.fromhex(path[1]['encrypted_recipient_data'])) + final_tlvs.add_field(18, tu64_encode(1000)) + + hops = [{'pubkey': l2.info['id'], + 'payload': mid_tlvs.to_bytes().hex()}, + {'pubkey': path[1]['blinded_node_id'], + 'payload': final_tlvs.to_bytes().hex()}] + onion = l1.rpc.createonion(hops=hops, assocdata=decoded['invoice_payment_hash']) + + ret = l2.rpc.injectpaymentonion(onion=onion['onion'], + payment_hash=decoded['invoice_payment_hash'], + amount_msat=1001, + cltv_expiry=blockheight + 18 + 6, + partid=1, + groupid=0) + assert sha256(bytes.fromhex(ret['payment_preimage'])).hexdigest() == decoded['invoice_payment_hash'] + # The label for the invoice is deterministic. + label = f"{decoded['offer_id']}-{decoded['invreq_payer_id']}-0" + assert only_one(l4.rpc.listinvoices(label)['invoices'])['status'] == 'paid' + lsp = only_one(l2.rpc.listsendpays(inv9['invoice'])['payments']) + assert lsp['groupid'] == 0 + assert lsp['partid'] == 1 + assert lsp['payment_hash'] == decoded['invoice_payment_hash'] + assert lsp['status'] == 'complete' + + +def test_injectpaymentonion_failures(node_factory, executor): + l1, l2 = node_factory.line_graph(2, wait_for_announce=True) + blockheight = l1.rpc.getinfo()['blockheight'] + + # + # Failure cases should give an onion: + # Unknown invoice. + # Unknown invoice (selfpay) + # Cannot forward. + + # Unknown invoice + hops = [{'pubkey': l1.info['id'], + 'payload': serialize_payload_tlv(1000, 18 + 6, first_scid(l1, l2), blockheight).hex()}, + {'pubkey': l2.info['id'], + 'payload': serialize_payload_final_tlv(1000, 18, 1000, blockheight, '00' * 32).hex()}] + onion = l1.rpc.createonion(hops=hops, assocdata='00' * 32) + + with pytest.raises(RpcError) as err: + l1.rpc.injectpaymentonion(onion=onion['onion'], + payment_hash='00' * 32, + amount_msat=1000, + cltv_expiry=blockheight + 18 + 6, + partid=1, + groupid=0) + + # PAY_INJECTPAYMENTONION_FAILED + assert err.value.error['code'] == 218 + assert 'onionreply' in err.value.error['data'] + + # Self-pay (unknown payment_hash) + hops = [{'pubkey': l1.info['id'], + 'payload': serialize_payload_final_tlv(1000, 18, 1000, blockheight, '00' * 32).hex()}] + onion = l1.rpc.createonion(hops=hops, assocdata='00' * 32) + + with pytest.raises(RpcError) as err: + l1.rpc.injectpaymentonion(onion=onion['onion'], + payment_hash='00' * 32, + amount_msat=1000, + cltv_expiry=blockheight + 18 + 6, + partid=1, + groupid=1) + + # PAY_INJECTPAYMENTONION_FAILED + assert err.value.error['code'] == 218 + assert 'onionreply' in err.value.error['data'] + + # Insufficient funds (l2 can't pay to l1) + inv11 = l1.rpc.invoice(3000, "test_injectpaymentonion11", "test_injectpaymentonion11") + hops = [{'pubkey': l2.info['id'], + 'payload': serialize_payload_tlv(1000, 18 + 6, first_scid(l1, l2), blockheight).hex()}, + {'pubkey': l1.info['id'], + 'payload': serialize_payload_final_tlv(1000, 18, 1000, blockheight, inv11['payment_secret']).hex()}] + onion = l1.rpc.createonion(hops=hops, assocdata=inv11['payment_hash']) + + with pytest.raises(RpcError) as err: + l2.rpc.injectpaymentonion(onion=onion['onion'], + payment_hash=inv11['payment_hash'], + amount_msat=1000, + cltv_expiry=blockheight + 18 + 6, + partid=1, + groupid=0) + + # PAY_INJECTPAYMENTONION_FAILED + assert err.value.error['code'] == 218 + assert 'onionreply' in err.value.error['data'] diff --git a/tests/utils.py b/tests/utils.py index da26b53a8bc2..fb6ed1225e94 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -3,6 +3,8 @@ import bitstring from pyln.client import Millisatoshi from pyln.testing.utils import EXPERIMENTAL_DUAL_FUND, EXPERIMENTAL_SPLICING +from pyln.proto.onion import TlvPayload +import struct import subprocess import tempfile import time @@ -560,3 +562,56 @@ def write_dumb_template(outf, channels, propname, illegalvals=[]): nodemap[nodes[i]] = n return outfile, nodemap + + +def tu64_encode(i: int): + """Encode a tu64 (or tu32 etc) value""" + ret = struct.pack("!Q", i) + while ret.startswith(b'\0'): + ret = ret[1:] + return ret + + +def serialize_payload_tlv(amount_msat, delay, next_channel, blockheight): + """Encode TLV onion payload for non-final hops, returns bytes""" + block, tx, out = next_channel.split('x') + + payload = TlvPayload() + # BOLT #4: + # 1. type: 2 (`amt_to_forward`) + # 2. data: + # * [`tu64`:`amt_to_forward`] + payload.add_field(2, tu64_encode(int(amount_msat))) + # BOLT #4: + # 1. type: 4 (`outgoing_cltv_value`) + # 2. data: + # * [`tu32`:`outgoing_cltv_value`] + payload.add_field(4, tu64_encode(blockheight + delay)) + # BOLT #4: + # 1. type: 6 (`short_channel_id`) + # 2. data: + # * [`short_channel_id`:`short_channel_id`] + payload.add_field(6, struct.pack("!Q", int(block) << 40 | int(tx) << 16 | int(out))) + return payload.to_bytes() + + +def serialize_payload_final_tlv(amount_msat, delay, total_msat, blockheight, payment_secret: str): + """Encode TLV onion payload for final hop, returns bytes""" + payload = TlvPayload() + # BOLT #4: + # 1. type: 2 (`amt_to_forward`) + # 2. data: + # * [`tu64`:`amt_to_forward`] + payload.add_field(2, tu64_encode(int(amount_msat))) + # BOLT #4: + # 1. type: 4 (`outgoing_cltv_value`) + # 2. data: + # * [`tu32`:`outgoing_cltv_value`] + payload.add_field(4, tu64_encode(blockheight + delay)) + # BOLT #4: + # 1. type: 8 (`payment_data`) + # 2. data: + # * [`32*byte`:`payment_secret`] + # * [`tu64`:`total_msat`] + payload.add_field(8, bytes.fromhex(payment_secret) + tu64_encode(int(total_msat))) + return payload.to_bytes() diff --git a/wallet/test/run-wallet.c b/wallet/test/run-wallet.c index 9fe88a2cbf2a..af2196d3ef41 100644 --- a/wallet/test/run-wallet.c +++ b/wallet/test/run-wallet.c @@ -152,6 +152,9 @@ struct command_result *command_failed(struct command *cmd UNNEEDED, struct json_stream *result) { fprintf(stderr, "command_failed called!\n"); abort(); } +/* Generated stub for command_its_complicated */ +struct command_result *command_its_complicated(const char *why UNNEEDED) +{ fprintf(stderr, "command_its_complicated called!\n"); abort(); } /* Generated stub for command_param_failed */ struct command_result *command_param_failed(void) @@ -359,12 +362,17 @@ bool htlc_is_trimmed(enum side htlc_owner UNNEEDED, bool option_anchor_outputs UNNEEDED, bool option_anchors_zero_fee_htlc_tx UNNEEDED) { fprintf(stderr, "htlc_is_trimmed called!\n"); abort(); } -/* Generated stub for htlc_set_add */ -void htlc_set_add(struct lightningd *ld UNNEEDED, - struct htlc_in *hin UNNEEDED, - struct amount_msat total_msat UNNEEDED, - const struct secret *payment_secret UNNEEDED) -{ fprintf(stderr, "htlc_set_add called!\n"); abort(); } +/* Generated stub for htlc_set_add_ */ +void htlc_set_add_(struct lightningd *ld UNNEEDED, + struct logger *log UNNEEDED, + struct amount_msat msat UNNEEDED, + struct amount_msat total_msat UNNEEDED, + const struct sha256 *payment_hash UNNEEDED, + const struct secret *payment_secret UNNEEDED, + void (*fail)(void * UNNEEDED, const u8 *) UNNEEDED, + void (*succeeded)(void * UNNEEDED, const struct preimage *) UNNEEDED, + void *arg UNNEEDED) +{ fprintf(stderr, "htlc_set_add_ called!\n"); abort(); } /* Generated stub for invoice_check_payment */ const struct invoice_details *invoice_check_payment(const tal_t *ctx UNNEEDED, struct lightningd *ld UNNEEDED, @@ -835,6 +843,11 @@ struct command_result *param_number(struct command *cmd UNNEEDED, const char *na const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, unsigned int **num UNNEEDED) { fprintf(stderr, "param_number called!\n"); abort(); } +/* Generated stub for param_pubkey */ +struct command_result *param_pubkey(struct command *cmd UNNEEDED, const char *name UNNEEDED, + const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, + struct pubkey **pubkey UNNEEDED) +{ fprintf(stderr, "param_pubkey called!\n"); abort(); } /* Generated stub for param_secret */ struct command_result *param_secret(struct command *cmd UNNEEDED, const char *name UNNEEDED, const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,