diff --git a/plugins/renepay/Makefile b/plugins/renepay/Makefile index 38406183df23..5300fecb0f04 100644 --- a/plugins/renepay/Makefile +++ b/plugins/renepay/Makefile @@ -10,6 +10,7 @@ PLUGIN_RENEPAY_SRC := \ plugins/renepay/routebuilder.c \ plugins/renepay/routetracker.c \ plugins/renepay/routefail.c \ + plugins/renepay/sendpay.c \ plugins/renepay/uncertainty.c \ plugins/renepay/mods.c \ plugins/renepay/errorcodes.c \ @@ -29,6 +30,7 @@ PLUGIN_RENEPAY_HDRS := \ plugins/renepay/routebuilder.h \ plugins/renepay/routetracker.h \ plugins/renepay/routefail.h \ + plugins/renepay/sendpay.h \ plugins/renepay/uncertainty.h \ plugins/renepay/mods.h \ plugins/renepay/errorcodes.h \ @@ -43,6 +45,6 @@ PLUGIN_ALL_HEADER += $(PLUGIN_RENEPAY_HDRS) # Make all plugins depend on all plugin headers, for simplicity. $(PLUGIN_RENEPAY_OBJS): $(PLUGIN_RENEPAY_HDRS) -plugins/cln-renepay: $(PLUGIN_RENEPAY_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) $(CCAN_OBJS) bitcoin/chainparams.o common/gossmap.o common/gossmods_listpeerchannels.o common/fp16.o common/dijkstra.o common/bolt12.o common/bolt12_merkle.o common/sciddir_or_pubkey.o wire/bolt12_wiregen.o wire/onion_wiregen.o +plugins/cln-renepay: $(PLUGIN_RENEPAY_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) $(CCAN_OBJS) bitcoin/chainparams.o common/gossmap.o common/gossmods_listpeerchannels.o common/fp16.o common/dijkstra.o common/bolt12.o common/bolt12_merkle.o common/sciddir_or_pubkey.o wire/bolt12_wiregen.o wire/onion_wiregen.o common/sphinx.o common/onion_encode.o common/hmac.o common/onionreply.o include plugins/renepay/test/Makefile diff --git a/plugins/renepay/json.c b/plugins/renepay/json.c index 1e95165f70d9..e48388fb308e 100644 --- a/plugins/renepay/json.c +++ b/plugins/renepay/json.c @@ -1,5 +1,7 @@ #include "config.h" #include +#include +#include #include /* See if this notification is about one of our flows. */ @@ -50,7 +52,7 @@ struct route *tal_route_from_json(const tal_t *ctx, const char *buf, goto fail; if (!json_to_sha256(buf, hashtok, &route->key.payment_hash)) goto fail; - if (!json_to_msat(buf, amttok, &route->amount)) + if (!json_to_msat(buf, amttok, &route->amount_deliver)) goto fail; if (!json_to_msat(buf, senttok, &route->amount_sent)) goto fail; @@ -64,6 +66,7 @@ struct route *tal_route_from_json(const tal_t *ctx, const char *buf, route->hops = NULL; route->final_msg = NULL; route->final_error = LIGHTNINGD; + route->shared_secrets = NULL; return route; fail: @@ -71,11 +74,104 @@ struct route *tal_route_from_json(const tal_t *ctx, const char *buf, return tal_free(route); } +static bool get_data_details_onionreply(struct payment_result *result, + const char *buffer, + const jsmntok_t *datatok, + struct secret *shared_secrets) +{ + const tal_t *this_ctx = tal(result, tal_t); + const jsmntok_t *onionreplytok; + struct onionreply *onionreply, *wonionreply; + const u8 *replymsg; + int index; + + onionreplytok = json_get_member(buffer, datatok, "onionreply"); + if (!onionreplytok || !shared_secrets) + goto fail; + onionreply = new_onionreply( + this_ctx, + take(json_tok_bin_from_hex(this_ctx, buffer, onionreplytok))); + assert(onionreply); + /* FIXME: It seems that lightningd will unwrap top portion of the + * onionreply for us before serializing it, while unwrap_onionreply will + * try to do the entire unwraping. It would be a better API if either + * lightningd unwraps the entire thing or it doesn't do any unwraping. + * Also it wouldn't hurt if injectpaymentonion accepted the shared + * secrets to allow lightningd do the decoding for us. */ + wonionreply = wrap_onionreply(this_ctx, &shared_secrets[0], onionreply); + replymsg = unwrap_onionreply(this_ctx, shared_secrets, + tal_count(shared_secrets), + wonionreply, &index); + if (replymsg) { + result->failcode = tal(result, enum onion_wire); + *result->failcode = fromwire_peektype(replymsg); + + result->erring_index = tal(result, u32); + *result->erring_index = index; + } + tal_free(this_ctx); + return true; +fail: + tal_free(this_ctx); + return false; +} + +static bool get_data_details(struct payment_result *result, + const char *buffer, + const jsmntok_t *datatok) +{ + + const jsmntok_t *erridxtok, *failcodetok, *errnodetok, *errchantok, + *errdirtok, *rawmsgtok, *failcodenametok; + erridxtok = json_get_member(buffer, datatok, "erring_index"); + failcodetok = json_get_member(buffer, datatok, "failcode"); + + if (!erridxtok || !failcodetok) + return false; + result->failcode = tal(result, enum onion_wire); + json_to_u32(buffer, failcodetok, result->failcode); + + result->erring_index = tal(result, u32); + json_to_u32(buffer, erridxtok, result->erring_index); + + // search for other fields + errnodetok = json_get_member(buffer, datatok, "erring_node"); + errchantok = json_get_member(buffer, datatok, "erring_channel"); + errdirtok = json_get_member(buffer, datatok, "erring_direction"); + failcodenametok = json_get_member(buffer, datatok, "failcodename"); + rawmsgtok = json_get_member(buffer, datatok, "raw_message"); + + if (errnodetok != NULL) { + result->erring_node = tal(result, struct node_id); + json_to_node_id(buffer, errnodetok, result->erring_node); + } + + if (errchantok != NULL) { + result->erring_channel = tal(result, struct short_channel_id); + json_to_short_channel_id(buffer, errchantok, + result->erring_channel); + } + if (errdirtok != NULL) { + result->erring_direction = tal(result, int); + json_to_int(buffer, errdirtok, result->erring_direction); + } + if (rawmsgtok != NULL) + result->raw_message = + json_tok_bin_from_hex(result, buffer, rawmsgtok); + + if (failcodenametok != NULL) + result->failcodename = + json_strdup(result, buffer, failcodenametok); + + return true; +} + struct payment_result *tal_sendpay_result_from_json(const tal_t *ctx, const char *buffer, - const jsmntok_t *toks) + const jsmntok_t *toks, + struct secret *shared_secrets) { - const jsmntok_t *idtok = json_get_member(buffer, toks, "id"); + const jsmntok_t *idtok = json_get_member(buffer, toks, "created_index"); const jsmntok_t *hashtok = json_get_member(buffer, toks, "payment_hash"); const jsmntok_t *senttok = @@ -84,28 +180,33 @@ struct payment_result *tal_sendpay_result_from_json(const tal_t *ctx, const jsmntok_t *preimagetok = json_get_member(buffer, toks, "payment_preimage"); const jsmntok_t *codetok = json_get_member(buffer, toks, "code"); + const jsmntok_t *msgtok = json_get_member(buffer, toks, "message"); const jsmntok_t *datatok = json_get_member(buffer, toks, "data"); - const jsmntok_t *erridxtok, *msgtok, *failcodetok, *rawmsgtok, - *failcodenametok, *errchantok, *errnodetok, *errdirtok; struct payment_result *result; /* Check if we have an error and need to descend into data to get * details. */ if (codetok != NULL && datatok != NULL) { - idtok = json_get_member(buffer, datatok, "id"); + idtok = json_get_member(buffer, datatok, "create_index"); hashtok = json_get_member(buffer, datatok, "payment_hash"); senttok = json_get_member(buffer, datatok, "amount_sent_msat"); statustok = json_get_member(buffer, datatok, "status"); } /* Initial sanity checks, all these fields must exist. */ - if (idtok == NULL || idtok->type != JSMN_PRIMITIVE || hashtok == NULL || - hashtok->type != JSMN_STRING || senttok == NULL || - statustok == NULL || statustok->type != JSMN_STRING) { + if (hashtok == NULL || hashtok->type != JSMN_STRING || + senttok == NULL || statustok == NULL || + statustok->type != JSMN_STRING) { return NULL; } result = tal(ctx, struct payment_result); + memset(result, 0, sizeof(struct payment_result)); + + if (msgtok) + result->message = json_strdup(result, buffer, msgtok); + else + result->message = NULL; if (codetok != NULL) // u32? isn't this an int? @@ -114,7 +215,12 @@ struct payment_result *tal_sendpay_result_from_json(const tal_t *ctx, else result->code = 0; - json_to_u64(buffer, idtok, &result->id); + if (idtok) { + result->created_index = tal(result, u64); + json_to_u64(buffer, idtok, result->created_index); + } else + result->created_index = NULL; + json_to_msat(buffer, senttok, &result->amount_sent); if (json_tok_streq(buffer, statustok, "pending")) { result->status = SENDPAY_PENDING; @@ -132,77 +238,13 @@ struct payment_result *tal_sendpay_result_from_json(const tal_t *ctx, } /* Now extract the error details if the error code is not 0 */ - if (result->code != 0) { - erridxtok = json_get_member(buffer, datatok, "erring_index"); - errnodetok = json_get_member(buffer, datatok, "erring_node"); - errchantok = json_get_member(buffer, datatok, "erring_channel"); - errdirtok = - json_get_member(buffer, datatok, "erring_direction"); - failcodetok = json_get_member(buffer, datatok, "failcode"); - failcodenametok = - json_get_member(buffer, datatok, "failcodename"); - msgtok = json_get_member(buffer, toks, "message"); - rawmsgtok = json_get_member(buffer, datatok, "raw_message"); - if (failcodetok == NULL || - failcodetok->type != JSMN_PRIMITIVE || - (failcodenametok != NULL && - failcodenametok->type != JSMN_STRING) || - (erridxtok != NULL && erridxtok->type != JSMN_PRIMITIVE) || - (errnodetok != NULL && errnodetok->type != JSMN_STRING) || - (errchantok != NULL && errchantok->type != JSMN_STRING) || - (errdirtok != NULL && errdirtok->type != JSMN_PRIMITIVE) || - msgtok == NULL || msgtok->type != JSMN_STRING || - (rawmsgtok != NULL && rawmsgtok->type != JSMN_STRING)) + if (result->code != 0 && datatok) { + /* try one, then try the other, then fail */ + if (!get_data_details(result, buffer, datatok) && + !get_data_details_onionreply(result, buffer, datatok, + shared_secrets)) goto fail; - - if (rawmsgtok != NULL) - result->raw_message = - json_tok_bin_from_hex(result, buffer, rawmsgtok); - else - result->raw_message = NULL; - - if (failcodenametok != NULL) - result->failcodename = - json_strdup(result, buffer, failcodenametok); - else - result->failcodename = NULL; - - json_to_u32(buffer, failcodetok, &result->failcode); - result->message = json_strdup(result, buffer, msgtok); - - if (erridxtok != NULL) { - result->erring_index = tal(result, u32); - json_to_u32(buffer, erridxtok, result->erring_index); - } else { - result->erring_index = NULL; - } - - if (errdirtok != NULL) { - result->erring_direction = tal(result, int); - json_to_int(buffer, errdirtok, - result->erring_direction); - } else { - result->erring_direction = NULL; - } - - if (errnodetok != NULL) { - result->erring_node = tal(result, struct node_id); - json_to_node_id(buffer, errnodetok, - result->erring_node); - } else { - result->erring_node = NULL; - } - - if (errchantok != NULL) { - result->erring_channel = - tal(result, struct short_channel_id); - json_to_short_channel_id(buffer, errchantok, - result->erring_channel); - } else { - result->erring_channel = NULL; - } } - return result; fail: return tal_free(result); @@ -327,3 +369,27 @@ void json_add_route(struct json_stream *js, const struct route *route, json_add_string(js, "description", pinfo->description); } + +void json_myadd_blinded_path(struct json_stream *s, + const char *fieldname, + const struct blinded_path *blinded_path) +{ + // FIXME: how can we support the case when the entry point is a + // scid? + assert(blinded_path->first_node_id.is_pubkey); + json_object_start(s, fieldname); + json_add_pubkey(s, "first_node_id", + &blinded_path->first_node_id.pubkey); + json_add_pubkey(s, "first_path_key", &blinded_path->first_path_key); + json_array_start(s, "path"); + for (size_t i = 0; i < tal_count(blinded_path->path); i++) { + const struct blinded_path_hop *hop = blinded_path->path[i]; + json_object_start(s, NULL); + json_add_pubkey(s, "blinded_node_id", &hop->blinded_node_id); + json_add_hex_talarr(s, "encrypted_recipient_data", + hop->encrypted_recipient_data); + json_object_end(s); + } + json_array_end(s); + json_object_end(s); +} diff --git a/plugins/renepay/json.h b/plugins/renepay/json.h index d08749c2c0f5..5abaa8348997 100644 --- a/plugins/renepay/json.h +++ b/plugins/renepay/json.h @@ -13,11 +13,16 @@ struct route *tal_route_from_json(const tal_t *ctx, const char *buf, struct payment_result *tal_sendpay_result_from_json(const tal_t *ctx, const char *buffer, - const jsmntok_t *toks); + const jsmntok_t *toks, + struct secret *shared_secrets); void json_add_payment(struct json_stream *s, const struct payment *payment); void json_add_route(struct json_stream *s, const struct route *route, const struct payment *payment); +void json_myadd_blinded_path(struct json_stream *s, + const char *fieldname, + const struct blinded_path *blinded_path); + #endif /* LIGHTNING_PLUGINS_RENEPAY_JSON_H */ diff --git a/plugins/renepay/main.c b/plugins/renepay/main.c index c9b6e65bde92..0fb75f3aed61 100644 --- a/plugins/renepay/main.c +++ b/plugins/renepay/main.c @@ -17,6 +17,7 @@ #include #include #include +#include #include // TODO(eduardo): notice that pending attempts performed with another @@ -169,6 +170,7 @@ static struct command_result *json_pay(struct command *cmd, const char *buf, const char *invstr; struct amount_msat *msat; struct amount_msat *maxfee; + struct amount_msat *inv_msat = NULL; u32 *maxdelay; u32 *retryfor; const char *description; @@ -189,6 +191,9 @@ static struct command_result *json_pay(struct command *cmd, const char *buf, * than zero. */ u64 *base_prob_success_millionths; + u64 invexpiry; + struct sha256 *payment_hash = NULL; + if (!param(cmd, buf, params, p_req("invstring", param_invstring, &invstr), p_opt("amount_msat", param_msat, &msat), @@ -206,9 +211,6 @@ static struct command_result *json_pay(struct command *cmd, const char *buf, p_opt("label", param_string, &label), p_opt("exclude", param_route_exclusion_array, &exclusions), - // FIXME add support for offers - // p_opt("localofferid", param_sha256, &local_offer_id), - p_opt_dev("dev_use_shadow", param_bool, &use_shadow, true), // MCF options @@ -237,38 +239,56 @@ static struct command_result *json_pay(struct command *cmd, const char *buf, /* === Parse invoice === */ - // FIXME: add support for bolt12 invoices - if (bolt12_has_prefix(invstr)) - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "BOLT12 invoices are not yet supported."); - char *fail; - struct bolt11 *b11 = - bolt11_decode(tmpctx, invstr, plugin_feature_set(cmd->plugin), - description, chainparams, &fail); - if (b11 == NULL) - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "Invalid bolt11: %s", fail); + struct bolt11 *b11 = NULL; + struct tlv_invoice *b12 = NULL; + + if (bolt12_has_prefix(invstr)) { + b12 = invoice_decode(tmpctx, invstr, strlen(invstr), + plugin_feature_set(cmd->plugin), + chainparams, &fail); + if (b12 == NULL) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Invalid bolt12 invoice: %s", fail); - /* Sanity check */ - if (feature_offered(b11->features, OPT_VAR_ONION) && - !b11->payment_secret) - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "Invalid bolt11:" - " sets feature var_onion with no secret"); + invexpiry = invoice_expiry(b12); + if (b12->invoice_amount) { + inv_msat = tal(tmpctx, struct amount_msat); + *inv_msat = amount_msat(*b12->invoice_amount); + } + payment_hash = b12->invoice_payment_hash; + } else { + b11 = bolt11_decode(tmpctx, invstr, + plugin_feature_set(cmd->plugin), + description, chainparams, &fail); + if (b11 == NULL) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Invalid bolt11 invoice: %s", fail); - if (b11->msat) { - // amount is written in the invoice - if (msat) + /* Sanity check */ + if (feature_offered(b11->features, OPT_VAR_ONION) && + !b11->payment_secret) return command_fail( cmd, JSONRPC2_INVALID_PARAMS, - "amount_msat parameter unnecessary"); - msat = b11->msat; - } else { - // amount is not written in the invoice - if (!msat) + "Invalid bolt11 invoice:" + " sets feature var_onion with no secret"); + inv_msat = b11->msat; + invexpiry = b11->timestamp + b11->expiry; + payment_hash = &b11->payment_hash; + } + + /* === Set default values for non-trivial constraints === */ + + // Obtain amount from invoice or from arguments + if (msat && inv_msat) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "amount_msat parameter cannot be specified " + "on an invoice with an amount"); + if (!msat) { + if (!inv_msat) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "amount_msat parameter required"); + msat = tal_dup(tmpctx, struct amount_msat, inv_msat); } // Default max fee is 5 sats, or 0.5%, whichever is *higher* @@ -278,46 +298,104 @@ static struct command_result *json_pay(struct command *cmd, const char *buf, fee = AMOUNT_MSAT(5000); maxfee = tal_dup(tmpctx, struct amount_msat, &fee); } + assert(msat); + assert(maxfee); + assert(maxdelay); + assert(retryfor); + assert(use_shadow); + assert(base_fee_penalty_millionths); + assert(prob_cost_factor_millionths); + assert(riskfactor_millionths); + assert(min_prob_success_millionths); + assert(base_prob_success_millionths); + + /* === Is it expired? === */ const u64 now_sec = time_now().ts.tv_sec; - if (now_sec > (b11->timestamp + b11->expiry)) + if (now_sec > invexpiry) return command_fail(cmd, PAY_INVOICE_EXPIRED, "Invoice expired"); /* === Get payment === */ // one payment_hash one payment is not assumed, it is enforced + assert(payment_hash); struct payment *payment = - payment_map_get(pay_plugin->payment_map, b11->payment_hash); + payment_map_get(pay_plugin->payment_map, *payment_hash); if(!payment) { - payment = payment_new( - tmpctx, - &b11->payment_hash, - take(invstr), - take(label), - take(description), - b11->payment_secret, - b11->metadata, - cast_const2(const struct route_info**, b11->routes), - &b11->receiver_id, - *msat, - *maxfee, - *maxdelay, - *retryfor, - b11->min_final_cltv_expiry, - *base_fee_penalty_millionths, - *prob_cost_factor_millionths, - *riskfactor_millionths, - *min_prob_success_millionths, - *base_prob_success_millionths, - use_shadow, - cast_const2(const struct route_exclusion**, exclusions)); - + payment = payment_new(tmpctx, payment_hash, invstr); if (!payment) return command_fail(cmd, PLUGIN_ERROR, "failed to create a new payment"); + + struct payment_info *pinfo = &payment->payment_info; + pinfo->label = tal_strdup_or_null(payment, label); + pinfo->description = tal_strdup_or_null(payment, description); + + if (b11) { + pinfo->payment_secret = + tal_steal(payment, b11->payment_secret); + pinfo->payment_metadata = + tal_steal(payment, b11->metadata); + pinfo->routehints = tal_steal(payment, b11->routes); + pinfo->destination = b11->receiver_id; + pinfo->final_cltv = b11->min_final_cltv_expiry; + + pinfo->blinded_paths = NULL; + pinfo->blinded_payinfos = NULL; + } else { + pinfo->payment_secret = NULL; + pinfo->routehints = NULL; + pinfo->payment_metadata = NULL; + + pinfo->blinded_paths = + tal_steal(payment, b12->invoice_paths); + pinfo->blinded_payinfos = + tal_steal(payment, b12->invoice_blindedpay); + + node_id_from_pubkey(&pinfo->destination, + b12->invoice_node_id); + + /* FIXME: there is a different cltv_final for each + * blinded path, can we send this information to + * askrene? */ + u32 max_final_cltv = 0; + for (size_t i = 0; i < tal_count(pinfo->blinded_payinfos); + i++) { + u32 final_cltv = + pinfo->blinded_payinfos[i]->cltv_expiry_delta; + if (max_final_cltv < final_cltv) + max_final_cltv = final_cltv; + } + pinfo->final_cltv = max_final_cltv; + } + /* When dealing with BOLT12 blinded paths we compute the + * routing targeting a fake node to enable + * multi-destination minimum-cost-flow. Every blinded + * path entry node will be linked to this fake node + * using fake channels as well. This is also useful for + * solving self-payments. */ + payment->routing_destination = tal(payment, struct node_id); + if (!node_id_from_hexstr("0200000000000000000000000000000000000" + "00000000000000000000000000001", + 66, payment->routing_destination)) + abort(); + + if (!payment_set_constraints( + payment, *msat, *maxfee, *maxdelay, *retryfor, + *base_fee_penalty_millionths, + *prob_cost_factor_millionths, *riskfactor_millionths, + *min_prob_success_millionths, + *base_prob_success_millionths, use_shadow, + cast_const2(const struct route_exclusion **, + exclusions)) || + !payment_refresh(payment)) + return command_fail( + cmd, PLUGIN_ERROR, + "failed to update the payment parameters"); + if (!payment_register_command(payment, cmd)) return command_fail(cmd, PLUGIN_ERROR, "failed to register command"); @@ -338,20 +416,17 @@ static struct command_result *json_pay(struct command *cmd, const char *buf, } if (payment->status == PAYMENT_FAIL) { - // FIXME: should we refuse to pay if the invoices are different? - // or should we consider this a new payment? - if (!payment_update(payment, - *maxfee, - *maxdelay, - *retryfor, - b11->min_final_cltv_expiry, - *base_fee_penalty_millionths, - *prob_cost_factor_millionths, - *riskfactor_millionths, - *min_prob_success_millionths, - *base_prob_success_millionths, - use_shadow, - cast_const2(const struct route_exclusion**, exclusions))) + // FIXME: fail if invstring does not match + // FIXME: fail if payment_hash does not match + if (!payment_set_constraints( + payment, *msat, *maxfee, *maxdelay, *retryfor, + *base_fee_penalty_millionths, + *prob_cost_factor_millionths, *riskfactor_millionths, + *min_prob_success_millionths, + *base_prob_success_millionths, use_shadow, + cast_const2(const struct route_exclusion **, + exclusions)) || + !payment_refresh(payment)) return command_fail( cmd, PLUGIN_ERROR, "failed to update the payment parameters"); @@ -383,6 +458,10 @@ static const struct plugin_command commands[] = { "renepay", json_pay }, + { + "renesendpay", + json_renesendpay + }, }; static const struct plugin_notification notifications[] = { diff --git a/plugins/renepay/mods.c b/plugins/renepay/mods.c index aa43fa1314c6..f8650db16724 100644 --- a/plugins/renepay/mods.c +++ b/plugins/renepay/mods.c @@ -13,6 +13,7 @@ #include #include #include +#include #define INVALID_ID UINT32_MAX @@ -91,6 +92,13 @@ static struct command_result *payment_rpc_failure(struct command *cmd, json_tok_full_len(toks), json_tok_full(buffer, toks)); } +static void add_hintchan(struct payment *payment, const struct node_id *src, + const struct node_id *dst, u16 cltv_expiry_delta, + const struct short_channel_id scid, u32 fee_base_msat, + u32 fee_proportional_millionths, + const struct amount_msat *chan_htlc_min, + const struct amount_msat *chan_htlc_max); + /***************************************************************************** * previoussuccess * @@ -248,79 +256,23 @@ REGISTER_PAYMENT_MODIFIER(initial_sanity_checks, initial_sanity_checks_cb); /***************************************************************************** * selfpay - * - * Checks if the payment destination is the sender's node and perform a self - * payment. */ -static struct command_result *selfpay_success(struct command *cmd, - const char *method UNUSED, - const char *buf, - const jsmntok_t *tok, - struct route *route) -{ - tal_steal(tmpctx, route); // discard this route when tmpctx clears - struct payment *payment = - payment_map_get(pay_plugin->payment_map, route->key.payment_hash); - assert(payment); - - struct preimage preimage; - const char *err; - err = json_scan(tmpctx, buf, tok, "{payment_preimage:%}", - JSON_SCAN(json_to_preimage, &preimage)); - if (err) - plugin_err( - cmd->plugin, "selfpay didn't have payment_preimage: %.*s", - json_tok_full_len(tok), json_tok_full(buf, tok)); - - - payment_note(payment, LOG_DBG, "Paid with self-pay."); - return payment_success(payment, &preimage); -} -static struct command_result *selfpay_failure(struct command *cmd, - const char *method UNUSED, - const char *buf, - const jsmntok_t *tok, - struct route *route) -{ - tal_steal(tmpctx, route); // discard this route when tmpctx clears - struct payment *payment = - payment_map_get(pay_plugin->payment_map, route->key.payment_hash); - assert(payment); - struct payment_result *result = tal_sendpay_result_from_json(tmpctx, buf, tok); - if (result == NULL) - plugin_err(pay_plugin->plugin, - "Unable to parse sendpay failure: %.*s", - json_tok_full_len(tok), json_tok_full(buf, tok)); - - return payment_fail(payment, result->code, "%s", result->message); -} - static struct command_result *selfpay_cb(struct payment *payment) { - if (!node_id_eq(&pay_plugin->my_id, - &payment->payment_info.destination)) { - return payment_continue(payment); + /* A different approach to self-pay: create a fake channel from the + * bolt11 destination to the routing_destination (a fake node_id). */ + if (!payment->payment_info.blinded_paths) { + struct amount_msat htlc_min = AMOUNT_MSAT(0); + struct amount_msat htlc_max = AMOUNT_MSAT((u64)1000*100000000); + struct short_channel_id scid = {.u64 = 0}; + add_hintchan(payment, &payment->payment_info.destination, + payment->routing_destination, + /* cltv delta = */ 0, scid, + /* base fee = */ 0, + /* ppm = */ 0, &htlc_min, &htlc_max); } - - struct command *cmd = payment_command(payment); - if (!cmd) - plugin_err(pay_plugin->plugin, - "Selfpay: cannot get a valid cmd."); - - struct payment_info *pinfo = &payment->payment_info; - /* Self-payment routes are not part of the routetracker, we build them - * on-the-fly here and release them on success or failure. */ - struct route *route = - new_route(payment, payment->groupid, - /*partid=*/0, pinfo->payment_hash, - pinfo->amount, pinfo->amount); - struct out_req *req; - req = jsonrpc_request_start(cmd, "sendpay", - selfpay_success, selfpay_failure, route); - route->hops = tal_arr(route, struct route_hop, 0); - json_add_route(req->js, route, payment); - return send_outreq(req); + return payment_continue(payment); } REGISTER_PAYMENT_MODIFIER(selfpay, selfpay_cb); @@ -503,24 +455,37 @@ REGISTER_PAYMENT_MODIFIER(refreshgossmap, refreshgossmap_cb); * Use route hints from the invoice to update the local gossmods and uncertainty * network. */ -// TODO check how this is done in pay.c + +static void uncertainty_remove_channel(struct chan_extra *ce, + struct uncertainty *uncertainty) +{ + chan_extra_map_del(uncertainty->chan_extra_map, ce); +} static void add_hintchan(struct payment *payment, const struct node_id *src, const struct node_id *dst, u16 cltv_expiry_delta, const struct short_channel_id scid, u32 fee_base_msat, - u32 fee_proportional_millionths) + u32 fee_proportional_millionths, + const struct amount_msat *chan_htlc_min, + const struct amount_msat *chan_htlc_max) { assert(payment); assert(payment->local_gossmods); const char *errmsg; - const struct chan_extra *ce = + struct chan_extra *ce = uncertainty_find_channel(pay_plugin->uncertainty, scid); if (!ce) { struct short_channel_id_dir scidd; /* We assume any HTLC is allowed */ struct amount_msat htlc_min = AMOUNT_MSAT(0), htlc_max = MAX_CAPACITY; + + if (chan_htlc_min) + htlc_min = *chan_htlc_min; + if (chan_htlc_max) + htlc_max = *chan_htlc_max; + struct amount_msat fee_base = amount_msat(fee_base_msat); bool enabled = true; scidd.scid = scid; @@ -555,6 +520,15 @@ static void add_hintchan(struct payment *payment, const struct node_id *src, fmt_short_channel_id(tmpctx, scid)); goto function_error; } + /* We want these channel hints destroyed when the local_gossmods + * are freed. */ + /* FIXME: these hints are global in the uncertainty network if + * two payments happen concurrently we will have race + * conditions. The best way to avoid this is to use askrene and + * it's layered API. */ + tal_steal(payment->local_gossmods, ce); + tal_add_destructor2(ce, uncertainty_remove_channel, + pay_plugin->uncertainty); } else { /* The channel is pubic and we already keep track of it in the * gossmap and uncertainty network. It would be wrong to assume @@ -584,7 +558,7 @@ static struct command_result *routehints_done(struct command *cmd UNUSED, assert(payment->local_gossmods); const struct node_id *destination = &payment->payment_info.destination; - const struct route_info **routehints = payment->payment_info.routehints; + struct route_info **routehints = payment->payment_info.routehints; assert(routehints); const size_t nhints = tal_count(routehints); /* Hints are added to the local_gossmods. */ @@ -597,7 +571,8 @@ static struct command_result *routehints_done(struct command *cmd UNUSED, add_hintchan(payment, &r[j].pubkey, end, r[j].cltv_expiry_delta, r[j].short_channel_id, r[j].fee_base_msat, - r[j].fee_proportional_millionths); + r[j].fee_proportional_millionths, + NULL, NULL); end = &r[j].pubkey; } } @@ -618,6 +593,8 @@ static struct command_result *routehints_done(struct command *cmd UNUSED, static struct command_result *routehints_cb(struct payment *payment) { + if (payment->payment_info.routehints == NULL) + return payment_continue(payment); struct command *cmd = payment_command(payment); assert(cmd); struct out_req *req = jsonrpc_request_start( @@ -629,6 +606,44 @@ static struct command_result *routehints_cb(struct payment *payment) REGISTER_PAYMENT_MODIFIER(routehints, routehints_cb); + +/***************************************************************************** + * blindedhints + * + * Similar to routehints but for bolt12 invoices: create fake channel that + * connect the blinded path entry point to the destination node. + */ + +static struct command_result *blindedhints_cb(struct payment *payment) +{ + if (payment->payment_info.blinded_paths == NULL) + return payment_continue(payment); + + struct payment_info *pinfo = &payment->payment_info; + struct short_channel_id scid; + struct node_id src; + + for (size_t i = 0; i < tal_count(pinfo->blinded_paths); i++) { + const struct blinded_payinfo *payinfo = + pinfo->blinded_payinfos[i]; + const struct blinded_path *path = pinfo->blinded_paths[i]; + + scid.u64 = i; // a fake scid + node_id_from_pubkey(&src, &path->first_node_id.pubkey); + + add_hintchan(payment, &src, payment->routing_destination, + payinfo->cltv_expiry_delta, scid, + payinfo->fee_base_msat, + payinfo->fee_proportional_millionths, + &payinfo->htlc_minimum_msat, + &payinfo->htlc_maximum_msat); + } + return payment_continue(payment); +} + +REGISTER_PAYMENT_MODIFIER(blindedhints, blindedhints_cb); + + /***************************************************************************** * compute_routes * @@ -687,6 +702,10 @@ static struct command_result *compute_routes_cb(struct payment *payment) * better to pass computed_routes as a reference? */ routetracker->computed_routes = tal_free(routetracker->computed_routes); + /* Send get_routes a note that it should discard the last hop because we + * are actually solving a multiple destinations problem. */ + bool blinded_destination = true; + // TODO: add an algorithm selector here /* We let this return an unlikely path, as it's better to try once than * simply refuse. Plus, models are not truth! */ @@ -694,7 +713,7 @@ static struct command_result *compute_routes_cb(struct payment *payment) routetracker, &payment->payment_info, &pay_plugin->my_id, - &payment->payment_info.destination, + payment->routing_destination, pay_plugin->gossmap, pay_plugin->uncertainty, payment->disabledmap, @@ -702,8 +721,10 @@ static struct command_result *compute_routes_cb(struct payment *payment) feebudget, &payment->next_partid, payment->groupid, + blinded_destination, &errcode, &err_msg); + /* Otherwise the error message remains a child of the routetracker. */ err_msg = tal_steal(tmpctx, err_msg); @@ -1218,25 +1239,26 @@ REGISTER_PAYMENT_CONDITION(retry, retry_cb); // add check pre-approved invoice void *payment_virtual_program[] = { /*0*/ OP_CALL, &previoussuccess_pay_mod, - /*2*/ OP_CALL, &selfpay_pay_mod, - /*4*/ OP_CALL, &knowledgerelax_pay_mod, - /*6*/ OP_CALL, &getmychannels_pay_mod, + /*2*/ OP_CALL, &knowledgerelax_pay_mod, + /*4*/ OP_CALL, &getmychannels_pay_mod, + /*6*/ OP_CALL, &selfpay_pay_mod, /*8*/ OP_CALL, &refreshgossmap_pay_mod, /*10*/ OP_CALL, &routehints_pay_mod, - /*12*/OP_CALL, &channelfilter_pay_mod, + /*12*/ OP_CALL, &blindedhints_pay_mod, + /*14*/OP_CALL, &channelfilter_pay_mod, // TODO shadow_additions /* do */ - /*14*/ OP_CALL, &pendingsendpays_pay_mod, - /*16*/ OP_CALL, &checktimeout_pay_mod, - /*18*/ OP_CALL, &refreshgossmap_pay_mod, - /*20*/ OP_CALL, &compute_routes_pay_mod, - /*22*/ OP_CALL, &send_routes_pay_mod, + /*16*/ OP_CALL, &pendingsendpays_pay_mod, + /*18*/ OP_CALL, &checktimeout_pay_mod, + /*20*/ OP_CALL, &refreshgossmap_pay_mod, + /*22*/ OP_CALL, &compute_routes_pay_mod, + /*24*/ OP_CALL, &send_routes_pay_mod, /*do*/ - /*24*/ OP_CALL, &sleep_pay_mod, - /*26*/ OP_CALL, &collect_results_pay_mod, + /*26*/ OP_CALL, &sleep_pay_mod, + /*28*/ OP_CALL, &collect_results_pay_mod, /*while*/ - /*28*/ OP_IF, ¬haveresults_pay_cond, (void *)24, + /*30*/ OP_IF, ¬haveresults_pay_cond, (void *)26, /* while */ - /*31*/ OP_IF, &retry_pay_cond, (void *)14, - /*34*/ OP_CALL, &end_pay_mod, /* safety net, default failure if reached */ - /*36*/ NULL}; + /*33*/ OP_IF, &retry_pay_cond, (void *)16, + /*36*/ OP_CALL, &end_pay_mod, /* safety net, default failure if reached */ + /*38*/ NULL}; diff --git a/plugins/renepay/payment.c b/plugins/renepay/payment.c index 58c18f670a2e..02566b01cdff 100644 --- a/plugins/renepay/payment.c +++ b/plugins/renepay/payment.c @@ -12,110 +12,37 @@ static struct command_result *payment_finish(struct payment *p); -struct payment *payment_new( - const tal_t *ctx, - const struct sha256 *payment_hash, - const char *invstr TAKES, - const char *label TAKES, - const char *description TAKES, - const struct secret *payment_secret TAKES, - const u8 *payment_metadata TAKES, - const struct route_info **routehints TAKES, - const struct node_id *destination, - struct amount_msat amount, - struct amount_msat maxfee, - unsigned int maxdelay, - u64 retryfor, - u16 final_cltv, - /* Tweakable in --developer mode */ - u64 base_fee_penalty_millionths, - u64 prob_cost_factor_millionths, - u64 riskfactor_millionths, - u64 min_prob_success_millionths, - u64 base_prob_success_millionths, - bool use_shadow, - const struct route_exclusion **exclusions) +struct payment *payment_new(const tal_t *ctx, const struct sha256 *payment_hash, + const char *invstr TAKES) { struct payment *p = tal(ctx, struct payment); + memset(p, 0, sizeof(struct payment)); + struct payment_info *pinfo = &p->payment_info; - /* === Unique properties === */ assert(payment_hash); pinfo->payment_hash = *payment_hash; assert(invstr); pinfo->invstr = tal_strdup(p, invstr); - pinfo->label = tal_strdup_or_null(p, label); - pinfo->description = tal_strdup_or_null(p, description); - pinfo->payment_secret = tal_dup_or_null(p, struct secret, payment_secret); - pinfo->payment_metadata = tal_dup_talarr(p, u8, payment_metadata); - - if (taken(routehints)) - pinfo->routehints = tal_steal(p, routehints); - else { - /* Deep copy */ - pinfo->routehints = - tal_dup_talarr(p, const struct route_info *, routehints); - for (size_t i = 0; i < tal_count(pinfo->routehints); i++) - pinfo->routehints[i] = - tal_steal(pinfo->routehints, pinfo->routehints[i]); - } - - assert(destination); - pinfo->destination = *destination; - pinfo->amount = amount; - - - /* === Payment attempt parameters === */ - if (!amount_msat_add(&pinfo->maxspend, amount, maxfee)) - pinfo->maxspend = AMOUNT_MSAT(UINT64_MAX); - pinfo->maxdelay = maxdelay; - - pinfo->start_time = time_now(); - pinfo->stop_time = timeabs_add(pinfo->start_time, time_from_sec(retryfor)); - - pinfo->final_cltv = final_cltv; - - /* === Developer options === */ - pinfo->base_fee_penalty = base_fee_penalty_millionths / 1e6; - pinfo->prob_cost_factor = prob_cost_factor_millionths / 1e6; - pinfo->delay_feefactor = riskfactor_millionths / 1e6; - pinfo->min_prob_success = min_prob_success_millionths / 1e6; - pinfo->base_prob_success = base_prob_success_millionths / 1e6; - pinfo->use_shadow = use_shadow; - - - /* === Public State === */ p->status = PAYMENT_PENDING; p->preimage = NULL; p->error_code = LIGHTNINGD; p->error_msg = NULL; p->total_sent = AMOUNT_MSAT(0); p->total_delivering = AMOUNT_MSAT(0); - p->paynotes = tal_arr(p, const char *, 0); + p->paynotes = tal_arr(p, const char*, 0); p->groupid = 1; - - /* === Hidden State === */ p->exec_state = INVALID_STATE; p->next_partid = 1; p->cmd_array = tal_arr(p, struct command *, 0); p->local_gossmods = NULL; p->disabledmap = disabledmap_new(p); - - for (size_t i = 0; i < tal_count(exclusions); i++) { - const struct route_exclusion *ex = exclusions[i]; - if (ex->type == EXCLUDE_CHANNEL) - disabledmap_add_channel(p->disabledmap, ex->u.chan_id); - else - disabledmap_add_node(p->disabledmap, ex->u.node_id); - } - p->have_results = false; p->retry = false; p->waitresult_timer = NULL; - p->routetracker = new_routetracker(p, p); return p; } @@ -137,13 +64,51 @@ static void payment_cleanup(struct payment *p) routetracker_cleanup(p->routetracker); } -bool payment_update( +/* Sets state values to ongoing payment */ +bool payment_refresh(struct payment *p){ + assert(p); + struct payment_info *pinfo = &p->payment_info; + /* === Public State === */ + p->status = PAYMENT_PENDING; + + /* I shouldn't be calling a payment_update on a payment that already + * succeed */ + assert(p->preimage == NULL); + + p->error_code = LIGHTNINGD; + p->error_msg = tal_free(p->error_msg); + p->total_sent = AMOUNT_MSAT(0); + p->total_delivering = AMOUNT_MSAT(0); + // p->paynotes are unchanged, they accumulate messages + p->groupid++; + + /* === Hidden State === */ + p->exec_state = INVALID_STATE; + p->next_partid = 1; + + /* I shouldn't be calling a payment_update on a payment that has pending + * cmds. */ + assert(p->cmd_array); + assert(tal_count(p->cmd_array) == 0); + + p->local_gossmods = tal_free(p->local_gossmods); + p->have_results = false; + p->retry = false; + p->waitresult_timer = tal_free(p->waitresult_timer); + + pinfo->start_time = time_now(); + pinfo->stop_time = + timeabs_add(pinfo->start_time, time_from_sec(pinfo->retryfor)); + + return true; +} + +bool payment_set_constraints( struct payment *p, + struct amount_msat amount, struct amount_msat maxfee, unsigned int maxdelay, u64 retryfor, - u16 final_cltv, - /* Tweakable in --developer mode */ u64 base_fee_penalty_millionths, u64 prob_cost_factor_millionths, u64 riskfactor_millionths, @@ -155,18 +120,13 @@ bool payment_update( assert(p); struct payment_info *pinfo = &p->payment_info; - /* === Unique properties === */ - // unchanged - /* === Payment attempt parameters === */ + pinfo->amount = amount; if (!amount_msat_add(&pinfo->maxspend, pinfo->amount, maxfee)) pinfo->maxspend = AMOUNT_MSAT(UINT64_MAX); pinfo->maxdelay = maxdelay; - pinfo->start_time = time_now(); - pinfo->stop_time = timeabs_add(pinfo->start_time, time_from_sec(retryfor)); - - pinfo->final_cltv = final_cltv; + pinfo->retryfor = retryfor; /* === Developer options === */ pinfo->base_fee_penalty = base_fee_penalty_millionths / 1e6; @@ -176,33 +136,6 @@ bool payment_update( pinfo->base_prob_success = base_prob_success_millionths / 1e6; pinfo->use_shadow = use_shadow; - - /* === Public State === */ - p->status = PAYMENT_PENDING; - - /* I shouldn't be calling a payment_update on a payment that already - * succeed */ - assert(p->preimage == NULL); - - p->error_code = LIGHTNINGD; - p->error_msg = tal_free(p->error_msg);; - p->total_sent = AMOUNT_MSAT(0); - p->total_delivering = AMOUNT_MSAT(0); - // p->paynotes are unchanged, they accumulate messages - p->groupid++; - - - /* === Hidden State === */ - p->exec_state = INVALID_STATE; - p->next_partid = 1; - - /* I shouldn't be calling a payment_update on a payment that has pending - * cmds. */ - assert(p->cmd_array); - assert(tal_count(p->cmd_array) == 0); - - p->local_gossmods = tal_free(p->local_gossmods); - assert(p->disabledmap); disabledmap_reset(p->disabledmap); @@ -214,10 +147,6 @@ bool payment_update( disabledmap_add_node(p->disabledmap, ex->u.node_id); } - p->have_results = false; - p->retry = false; - p->waitresult_timer = tal_free(p->waitresult_timer); - return true; } diff --git a/plugins/renepay/payment.h b/plugins/renepay/payment.h index d905841b8ac5..e3d30e301c10 100644 --- a/plugins/renepay/payment.h +++ b/plugins/renepay/payment.h @@ -56,6 +56,10 @@ struct payment { /* Localmods to apply to gossip_map for our own use. */ struct gossmap_localmods *local_gossmods; + /* Routes will be computed to reach this node, could be a fake node that + * we use to handle multiple blinded paths. */ + struct node_id *routing_destination; + struct disabledmap *disabledmap; /* Flag to indicate wether we have collected enough results to make a @@ -101,41 +105,23 @@ HTABLE_DEFINE_NODUPS_TYPE(struct payment, payment_hash, payment_hash64, struct payment *payment_new( const tal_t *ctx, const struct sha256 *payment_hash, - const char *invstr TAKES, - const char *label TAKES, - const char *description TAKES, - const struct secret *payment_secret TAKES, - const u8 *payment_metadata TAKES, - const struct route_info **routehints TAKES, - const struct node_id *destination, - struct amount_msat amount, - struct amount_msat maxfee, - unsigned int maxdelay, - u64 retryfor, - u16 final_cltv, - /* Tweakable in --developer mode */ - u64 base_fee_penalty_millionths, - u64 prob_cost_factor_millionths, - u64 riskfactor_millionths, - u64 min_prob_success_millionths, - u64 base_prob_success_millionths, - bool use_shadow, - const struct route_exclusion **exclusions); - -bool payment_update( - struct payment *p, - struct amount_msat maxfee, - unsigned int maxdelay, - u64 retryfor, - u16 final_cltv, - /* Tweakable in --developer mode */ - u64 base_fee_penalty_millionths, - u64 prob_cost_factor_millionths, - u64 riskfactor_millionths, - u64 min_prob_success_millionths, - u64 base_prob_success_millionths, - bool use_shadow, - const struct route_exclusion **exclusions); + const char *invstr TAKES); + +bool payment_set_constraints( + struct payment *p, + struct amount_msat amount, + struct amount_msat maxfee, + unsigned int maxdelay, + u64 retryfor, + u64 base_fee_penalty_millionths, + u64 prob_cost_factor_millionths, + u64 riskfactor_millionths, + u64 min_prob_success_millionths, + u64 base_prob_success_millionths, + bool use_shadow, + const struct route_exclusion **exclusions); + +bool payment_refresh(struct payment *p); struct amount_msat payment_sent(const struct payment *p); struct amount_msat payment_delivered(const struct payment *p); diff --git a/plugins/renepay/payment_info.h b/plugins/renepay/payment_info.h index 8906fc9285e9..c07bcdd29855 100644 --- a/plugins/renepay/payment_info.h +++ b/plugins/renepay/payment_info.h @@ -26,7 +26,11 @@ struct payment_info { const u8 *payment_metadata; /* Extracted routehints */ - const struct route_info **routehints; + struct route_info **routehints; + + /* blinded paths */ + struct blinded_path **blinded_paths; + struct blinded_payinfo **blinded_payinfos; /* How much, what, where */ struct node_id destination; @@ -44,6 +48,7 @@ struct payment_info { // see common/gossip_constants.h:8:#define ROUTING_MAX_HOPS 20 // int max_num_hops; + u64 retryfor; /* We promised this in pay() output */ struct timeabs start_time; diff --git a/plugins/renepay/route.c b/plugins/renepay/route.c index 06ac2526fa03..66e07cdb0436 100644 --- a/plugins/renepay/route.c +++ b/plugins/renepay/route.c @@ -3,7 +3,7 @@ struct route *new_route(const tal_t *ctx, u32 groupid, u32 partid, struct sha256 payment_hash, - struct amount_msat amount, + struct amount_msat amount_deliver, struct amount_msat amount_sent) { struct route *route = tal(ctx, struct route); @@ -17,8 +17,10 @@ struct route *new_route(const tal_t *ctx, u32 groupid, route->success_prob = 0.0; route->result = NULL; - route->amount = amount; + route->amount_deliver = amount_deliver; route->amount_sent = amount_sent; + route->path_num = -1; + route->shared_secrets = NULL; return route; } @@ -32,7 +34,8 @@ struct route *new_route(const tal_t *ctx, u32 groupid, struct route *flow_to_route(const tal_t *ctx, u32 groupid, u32 partid, struct sha256 payment_hash, u32 final_cltv, struct gossmap *gossmap, - struct flow *flow) + struct flow *flow, + bool blinded_destination) { struct route *route = new_route(ctx, groupid, partid, payment_hash, @@ -65,8 +68,14 @@ struct route *flow_to_route(const tal_t *ctx, goto function_fail; } route->success_prob = flow->success_prob; - route->amount = route->hops[pathlen - 1].amount; + route->amount_deliver = route->hops[pathlen - 1].amount; route->amount_sent = route->hops[0].amount; + + if (blinded_destination) { + route->path_num = route->hops[pathlen - 1].scid.u64; + tal_arr_remove(&route->hops, pathlen - 1); + } + return route; function_fail: @@ -85,7 +94,8 @@ struct route **flows_to_routes(const tal_t *ctx, for (size_t i = 0; i < N; i++) { routes[i] = flow_to_route(routes, groupid, partid++, - payment_hash, final_cltv, gossmap, flows[i]); + payment_hash, final_cltv, gossmap, flows[i], + false); if (!routes[i]) goto function_fail; } diff --git a/plugins/renepay/route.h b/plugins/renepay/route.h index 2aa9195980db..60d167c9a53f 100644 --- a/plugins/renepay/route.h +++ b/plugins/renepay/route.h @@ -29,13 +29,13 @@ enum sendpay_result_status { struct payment_result { /* DB internal id */ // TODO check all this variables - u64 id; + u64 *created_index; struct preimage *payment_preimage; enum sendpay_result_status status; struct amount_msat amount_sent; enum jsonrpc_errcode code; const char *failcodename; - enum onion_wire failcode; + enum onion_wire *failcode; const u8 *raw_message; const char *message; u32 *erring_index; @@ -59,15 +59,23 @@ struct route { /* The series of channels and nodes to traverse. */ struct route_hop *hops; + /* shared secrets to unwrap the onions */ + struct secret *shared_secrets; + /* amounts are redundant here if we know the hops, however sometimes we - * don't know the hops, eg. by calling listsendpays */ - struct amount_msat amount, amount_sent; + * don't know the hops, eg. by calling listsendpays, or if we have + * blinded paths */ + struct amount_msat amount_sent; + struct amount_msat amount_deliver; /* Probability estimate (0-1) */ double success_prob; /* result of waitsenday */ struct payment_result *result; + + /* blinded path index if any */ + int path_num; }; static inline struct routekey routekey(const struct sha256 *hash, u64 groupid, @@ -117,7 +125,8 @@ struct route *new_route(const tal_t *ctx, u32 groupid, struct route *flow_to_route(const tal_t *ctx, u32 groupid, u32 partid, struct sha256 payment_hash, u32 final_cltv, struct gossmap *gossmap, - struct flow *flow); + struct flow *flow, + bool blinded_destination); struct route **flows_to_routes(const tal_t *ctx, u32 groupid, u32 partid, @@ -138,11 +147,7 @@ const char *fmt_route_path(const tal_t *ctx, const struct route *route); static inline struct amount_msat route_delivers(const struct route *route) { assert(route); - if (route->hops && tal_count(route->hops) > 0) - assert(amount_msat_eq( - route->amount, - route->hops[tal_count(route->hops) - 1].amount)); - return route->amount; + return route->amount_deliver; } static inline struct amount_msat route_sends(const struct route *route) { @@ -165,7 +170,8 @@ static inline u32 route_delay(const struct route *route) { assert(route); assert(route->hops); - assert(tal_count(route->hops) > 0); + if (tal_count(route->hops) == 0) + return 0; const size_t pathlen = tal_count(route->hops); assert(route->hops[0].delay >= route->hops[pathlen - 1].delay); return route->hops[0].delay - route->hops[pathlen - 1].delay; diff --git a/plugins/renepay/routebuilder.c b/plugins/renepay/routebuilder.c index e128a881d64b..4e9d15142326 100644 --- a/plugins/renepay/routebuilder.c +++ b/plugins/renepay/routebuilder.c @@ -65,7 +65,10 @@ route_check_constraints(struct route *route, struct gossmap *gossmap, assert(route); assert(route->hops); const size_t pathlen = tal_count(route->hops); - if (!amount_msat_eq(route->amount, route->hops[pathlen - 1].amount)) + if (pathlen == 0) + return RENEPAY_NOERROR; + if (!amount_msat_eq(route->amount_deliver, + route->hops[pathlen - 1].amount)) return RENEPAY_PRECONDITION_ERROR; if (!amount_msat_eq(route->amount_sent, route->hops[0].amount)) return RENEPAY_PRECONDITION_ERROR; @@ -140,6 +143,7 @@ struct route **get_routes(const tal_t *ctx, u64 *next_partid, u64 groupid, + bool blinded_destination, enum jsonrpc_errcode *ecode, const char **fail) @@ -297,7 +301,8 @@ struct route **get_routes(const tal_t *ctx, struct route *r = flow_to_route( this_ctx, groupid, *next_partid, payment_info->payment_hash, - payment_info->final_cltv, gossmap, flows[i]); + payment_info->final_cltv, gossmap, flows[i], + blinded_destination); if (!r) { tal_report_error( diff --git a/plugins/renepay/routebuilder.h b/plugins/renepay/routebuilder.h index 2692d2a82adc..b7297e5410c4 100644 --- a/plugins/renepay/routebuilder.h +++ b/plugins/renepay/routebuilder.h @@ -23,6 +23,7 @@ struct route **get_routes(const tal_t *ctx, u64 *next_partid, u64 groupid, + bool blinded_destination, enum jsonrpc_errcode *ecode, const char **fail); diff --git a/plugins/renepay/routefail.c b/plugins/renepay/routefail.c index 29d515349565..4f450c6a4ace 100644 --- a/plugins/renepay/routefail.c +++ b/plugins/renepay/routefail.c @@ -126,18 +126,28 @@ static struct command_result *update_gossip_failure(struct command *cmd UNUSED, { assert(r); assert(r->payment); + assert(r->route->result->erring_index); + + const int index = *r->route->result->erring_index; + struct short_channel_id_dir scidd; + + if (r->route->result->erring_channel) { + scidd.scid = *r->route->result->erring_channel; + scidd.dir = *r->route->result->erring_direction; + } else if (r->route->hops) { + assert(index < tal_count(r->route->hops)); + const struct route_hop *hop = &r->route->hops[index]; + scidd.scid = hop->scid; + scidd.dir = hop->direction; + + } else /* don't have information to disable the erring channel */ + goto finish; - /* FIXME it might be too strong assumption that erring_channel should - * always be present here, but at least the documentation for - * waitsendpay says it is present in the case of error. */ - assert(r->route->result->erring_channel); - struct short_channel_id_dir scidd = { - .scid = *r->route->result->erring_channel, - .dir = *r->route->result->erring_direction}; payment_disable_chan( - r->payment, scidd, LOG_INFORM, - "addgossip failed (%.*s)", json_tok_full_len(result), - json_tok_full(buf, result)); + r->payment, scidd, LOG_INFORM, "addgossip failed (%.*s)", + json_tok_full_len(result), json_tok_full(buf, result)); + +finish: return update_gossip_done(cmd, method, buf, result, r); } @@ -188,6 +198,7 @@ static void route_final_error(struct route *route, enum jsonrpc_errcode error, route->final_msg = tal_strdup(route, what); } +/* FIXME: do proper error handling for BOLT12 */ static struct command_result *handle_failure(struct routefail *r) { /* BOLT #4: @@ -242,7 +253,22 @@ static struct command_result *handle_failure(struct routefail *r) if (route->hops) path_len = tal_count(route->hops); - assert(result->erring_index); + enum onion_wire failcode; + if(result->failcode) + failcode = *result->failcode; + else{ + payment_note( + payment, LOG_UNUSUAL, + "The failcode is unknown we skip error handling"); + goto finish; + } + + if (!result->erring_index) { + payment_note( + payment, LOG_UNUSUAL, + "The erring_index is unknown we skip error handling"); + goto finish; + } enum node_type node_type = UNKNOWN_NODE; if (route->hops) { @@ -254,76 +280,128 @@ static struct command_result *handle_failure(struct routefail *r) node_type = INTERMEDIATE_NODE; } - assert(result->erring_node); - - switch (result->failcode) { + switch (failcode) { // intermediate only case WIRE_INVALID_ONION_VERSION: case WIRE_INVALID_ONION_HMAC: case WIRE_INVALID_ONION_KEY: - if (node_type == FINAL_NODE) + switch (node_type) { + case FINAL_NODE: payment_note(payment, LOG_UNUSUAL, - "Final node %s reported strange " + "Final node reported strange " "error code %04x (%s)", - fmt_node_id(tmpctx, result->erring_node), - result->failcode, - onion_wire_name(result->failcode)); + failcode, + onion_wire_name(failcode)); + break; + case ORIGIN_NODE: + case INTERMEDIATE_NODE: + case UNKNOWN_NODE: + break; + } case WIRE_INVALID_ONION_BLINDING: - if (node_type == FINAL_NODE) { + switch (node_type) { + case FINAL_NODE: /* these errors from a final node mean a permanent * failure */ route_final_error( route, PAY_DESTINATION_PERM_FAIL, "Received error code %04x (%s) at final node.", - result->failcode, - onion_wire_name(result->failcode)); - } else if (node_type == INTERMEDIATE_NODE || - node_type == ORIGIN_NODE) { + failcode, + onion_wire_name(failcode)); + + break; + case INTERMEDIATE_NODE: + case ORIGIN_NODE: + if (!route->hops) + break; + /* we disable the next node in the hop */ assert(*result->erring_index < path_len); payment_disable_node( - payment, - route->hops[*result->erring_index].node_id, LOG_DBG, - "received %s from previous hop", - onion_wire_name(result->failcode)); + payment, route->hops[*result->erring_index].node_id, + LOG_DBG, "received %s from previous hop", + onion_wire_name(failcode)); + break; + case UNKNOWN_NODE: + break; } break; // final only case WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS: + switch (node_type) { + case FINAL_NODE: + route_final_error(route, PAY_DESTINATION_PERM_FAIL, + "Unknown invoice or wrong payment " + "details at destination."); + break; + case ORIGIN_NODE: + route_final_error( + route, PAY_UNSPECIFIED_ERROR, + "Error code %04x (%s) reported at the origin.", + failcode, + onion_wire_name(failcode)); + break; + case INTERMEDIATE_NODE: + if (!route->hops) + break; + payment_disable_node( + payment, + route->hops[*result->erring_index - 1].node_id, + LOG_INFORM, "received error %s", + onion_wire_name(failcode)); + break; + case UNKNOWN_NODE: + break; + } + break; case WIRE_FINAL_INCORRECT_HTLC_AMOUNT: case WIRE_FINAL_INCORRECT_CLTV_EXPIRY: - if (node_type == INTERMEDIATE_NODE) + switch (node_type) { + case INTERMEDIATE_NODE: payment_note(payment, LOG_UNUSUAL, - "Intermediate node %s reported strange " + "Intermediate node reported strange " "error code %04x (%s)", - fmt_node_id(tmpctx, result->erring_node), - result->failcode, - onion_wire_name(result->failcode)); + failcode, + onion_wire_name(failcode)); + break; + case ORIGIN_NODE: + case FINAL_NODE: + case UNKNOWN_NODE: + break; + } case WIRE_PERMANENT_NODE_FAILURE: case WIRE_REQUIRED_NODE_FEATURE_MISSING: case WIRE_TEMPORARY_NODE_FAILURE: case WIRE_INVALID_ONION_PAYLOAD: - - if (node_type == FINAL_NODE) { + switch (node_type) { + case FINAL_NODE: route_final_error( route, PAY_DESTINATION_PERM_FAIL, "Received error code %04x (%s) at final node.", - result->failcode, - onion_wire_name(result->failcode)); - } else if (node_type == ORIGIN_NODE) { + failcode, + onion_wire_name(failcode)); + break; + case ORIGIN_NODE: route_final_error( route, PAY_UNSPECIFIED_ERROR, "Error code %04x (%s) reported at the origin.", - result->failcode, - onion_wire_name(result->failcode)); - } else { - payment_disable_node(payment, - *result->erring_node, LOG_INFORM, - "received error %s", - onion_wire_name(result->failcode)); + failcode, + onion_wire_name(failcode)); + break; + case INTERMEDIATE_NODE: + if (!route->hops) + break; + payment_disable_node( + payment, + route->hops[*result->erring_index - 1].node_id, + LOG_INFORM, "received error %s", + onion_wire_name(failcode)); + break; + case UNKNOWN_NODE: + break; } break; @@ -333,49 +411,70 @@ static struct command_result *handle_failure(struct routefail *r) case WIRE_UNKNOWN_NEXT_PEER: case WIRE_EXPIRY_TOO_FAR: case WIRE_CHANNEL_DISABLED: - if (node_type == FINAL_NODE) { + switch (node_type) { + case FINAL_NODE: payment_note(payment, LOG_UNUSUAL, - "Final node %s reported strange " + "Final node reported strange " "error code %04x (%s)", - fmt_node_id(tmpctx, result->erring_node), - result->failcode, - onion_wire_name(result->failcode)); + failcode, + onion_wire_name(failcode)); route_final_error( route, PAY_DESTINATION_PERM_FAIL, "Received error code %04x (%s) at final node.", - result->failcode, - onion_wire_name(result->failcode)); + failcode, + onion_wire_name(failcode)); + + break; + case ORIGIN_NODE: + payment_note(payment, LOG_UNUSUAL, + "First node reported strange " + "error code %04x (%s)", + failcode, + onion_wire_name(failcode)); - } else { - assert(result->erring_channel); + break; + case INTERMEDIATE_NODE: + if (!route->hops) + break; struct short_channel_id_dir scidd = { - .scid = *result->erring_channel, - .dir = *result->erring_direction}; - payment_disable_chan( - payment, scidd, LOG_INFORM, - "%s", onion_wire_name(result->failcode)); + .scid = route->hops[*result->erring_index].scid, + .dir = route->hops[*result->erring_index].direction}; + payment_disable_chan(payment, scidd, LOG_INFORM, "%s", + onion_wire_name(failcode)); + + break; + case UNKNOWN_NODE: + break; } break; // final only case WIRE_MPP_TIMEOUT: - - if (node_type == INTERMEDIATE_NODE) { + switch (node_type) { + case INTERMEDIATE_NODE: /* Normally WIRE_MPP_TIMEOUT is raised by the final * node. If this is not the final node, then something * wrong is going on. We report it and disable that * node. */ payment_note(payment, LOG_UNUSUAL, - "Intermediate node %s reported strange " + "Intermediate node reported strange " "error code %04x (%s)", - fmt_node_id(tmpctx, result->erring_node), - result->failcode, - onion_wire_name(result->failcode)); - - payment_disable_node(payment, - *result->erring_node, LOG_INFORM, - "received error %s", - onion_wire_name(result->failcode)); + failcode, + onion_wire_name(failcode)); + + if (!route->hops) + break; + payment_disable_node( + payment, + route->hops[*result->erring_index - 1].node_id, + LOG_INFORM, "received error %s", + onion_wire_name(failcode)); + + break; + case ORIGIN_NODE: + case FINAL_NODE: + case UNKNOWN_NODE: + break; } break; @@ -384,59 +483,76 @@ static struct command_result *handle_failure(struct routefail *r) case WIRE_INCORRECT_CLTV_EXPIRY: case WIRE_FEE_INSUFFICIENT: case WIRE_AMOUNT_BELOW_MINIMUM: - - if (node_type == FINAL_NODE) { + switch (node_type) { + case FINAL_NODE: payment_note(payment, LOG_UNUSUAL, - "Final node %s reported strange " + "Final node reported strange " "error code %04x (%s)", - fmt_node_id(tmpctx, result->erring_node), - result->failcode, - onion_wire_name(result->failcode)); + failcode, + onion_wire_name(failcode)); route_final_error( route, PAY_DESTINATION_PERM_FAIL, "Received error code %04x (%s) at final node.", - result->failcode, - onion_wire_name(result->failcode)); + failcode, + onion_wire_name(failcode)); - } else { + break; + case ORIGIN_NODE: + payment_note(payment, LOG_UNUSUAL, + "First node reported strange " + "error code %04x (%s)", + failcode, + onion_wire_name(failcode)); + + break; + case INTERMEDIATE_NODE: + if (!route->hops) + break; /* Usually this means we need to update the channel * information and try again. To avoid hitting this * error again with the same channel we flag it. */ - assert(result->erring_channel); struct short_channel_id_dir scidd = { - .scid = *result->erring_channel, - .dir = *result->erring_direction}; - payment_warn_chan(payment, - scidd, LOG_INFORM, + .scid = route->hops[*result->erring_index].scid, + .dir = route->hops[*result->erring_index].direction}; + payment_warn_chan(payment, scidd, LOG_INFORM, "received error %s", - onion_wire_name(result->failcode)); - } + onion_wire_name(failcode)); + break; + case UNKNOWN_NODE: + break; + } break; // intermediate only case WIRE_TEMPORARY_CHANNEL_FAILURE: - - if (node_type == FINAL_NODE) { + switch (node_type) { + case FINAL_NODE: /* WIRE_TEMPORARY_CHANNEL_FAILURE could mean that the * next channel has not enough outbound liquidity or * cannot add another HTLC. A final node cannot raise * this error. */ payment_note(payment, LOG_UNUSUAL, - "Final node %s reported strange " + "Final node reported strange " "error code %04x (%s)", - fmt_node_id(tmpctx, result->erring_node), - result->failcode, - onion_wire_name(result->failcode)); + failcode, + onion_wire_name(failcode)); route_final_error( route, PAY_DESTINATION_PERM_FAIL, "Received error code %04x (%s) at final node.", - result->failcode, - onion_wire_name(result->failcode)); + failcode, + onion_wire_name(failcode)); + + break; + case INTERMEDIATE_NODE: + case ORIGIN_NODE: + case UNKNOWN_NODE: + break; } - break; } + +finish: return routefail_end(take(r)); } diff --git a/plugins/renepay/routetracker.c b/plugins/renepay/routetracker.c index 5309af1a8bf8..2e8b9ba2b08d 100644 --- a/plugins/renepay/routetracker.c +++ b/plugins/renepay/routetracker.c @@ -102,7 +102,7 @@ void route_failure_register(struct routetracker *routetracker, assert(result); /* Update the knowledge in the uncertaity network. */ - if (route->hops) { + if (route->hops && result->failcode) { assert(result->erring_index); int path_len = tal_count(route->hops); @@ -123,7 +123,7 @@ void route_failure_register(struct routetracker *routetracker, route->hops[i].direction); } - if (result->failcode == WIRE_TEMPORARY_CHANNEL_FAILURE && + if (*result->failcode == WIRE_TEMPORARY_CHANNEL_FAILURE && (last_good_channel + 1) < path_len) { /* A WIRE_TEMPORARY_CHANNEL_FAILURE could mean not * enough liquidity to forward the payment or cannot add @@ -195,13 +195,33 @@ static void route_pending_register(struct routetracker *routetracker, /* Callback function for sendpay request success. */ static struct command_result *sendpay_done(struct command *cmd, const char *method UNUSED, - const char *buf UNUSED, - const jsmntok_t *result UNUSED, + const char *buf, + const jsmntok_t *result, struct route *route) { assert(route); struct payment *payment = route_get_payment_verify(route); route_pending_register(payment->routetracker, route); + + const jsmntok_t *t; + size_t i; + bool ret; + + const jsmntok_t *secretstok = + json_get_member(buf, result, "shared_secrets"); + + if (secretstok) { + assert(secretstok->type == JSMN_ARRAY); + + route->shared_secrets = + tal_arr(route, struct secret, secretstok->size); + json_for_each_arr(i, t, secretstok) + { + ret = json_to_secret(buf, t, &route->shared_secrets[i]); + assert(ret); + } + } else + route->shared_secrets = NULL; return command_still_pending(cmd); } @@ -330,14 +350,54 @@ struct command_result *route_sendpay_request(struct command *cmd, struct route *route TAKES, struct payment *payment) { - struct out_req *req = - jsonrpc_request_start(cmd, "sendpay", - sendpay_done, sendpay_failed, route); + const struct payment_info *pinfo = &payment->payment_info; + struct out_req *req = jsonrpc_request_start( + cmd, "renesendpay", sendpay_done, sendpay_failed, route); + + const size_t pathlen = tal_count(route->hops); + json_add_sha256(req->js, "payment_hash", &pinfo->payment_hash); + json_add_u64(req->js, "partid", route->key.partid); + json_add_u64(req->js, "groupid", route->key.groupid); + json_add_string(req->js, "invoice", pinfo->invstr); + json_add_node_id(req->js, "destination", &pinfo->destination); + json_add_amount_msat(req->js, "amount_msat", route->amount_deliver); + json_add_amount_msat(req->js, "total_amount_msat", pinfo->amount); + json_add_u32(req->js, "final_cltv", pinfo->final_cltv); + + if (pinfo->label) + json_add_string(req->js, "label", pinfo->label); + if (pinfo->description) + json_add_string(req->js, "description", pinfo->description); + + json_array_start(req->js, "route"); + /* An empty route means a payment to oneself, pathlen=0 */ + for (size_t j = 0; j < pathlen; j++) { + const struct route_hop *hop = &route->hops[j]; + json_object_start(req->js, NULL); + json_add_node_id(req->js, "id", &hop->node_id); + json_add_short_channel_id(req->js, "channel", hop->scid); + json_add_amount_msat(req->js, "amount_msat", hop->amount); + json_add_num(req->js, "direction", hop->direction); + json_add_u32(req->js, "delay", hop->delay); + json_add_string(req->js, "style", "tlv"); + json_object_end(req->js); + } + json_array_end(req->js); + + /* Either we have a payment_secret for BOLT11 or blinded_paths for + * BOLT12 */ + if (pinfo->payment_secret) + json_add_secret(req->js, "payment_secret", pinfo->payment_secret); + else { + assert(pinfo->blinded_paths); + const struct blinded_path *bpath = + pinfo->blinded_paths[route->path_num]; + json_myadd_blinded_path(req->js, "blinded_path", bpath); - json_add_route(req->js, route, payment); + } route_map_add(payment->routetracker->sent_routes, route); - if(taken(route)) + if (taken(route)) tal_steal(payment->routetracker->sent_routes, route); return send_outreq(req); } @@ -385,7 +445,8 @@ struct command_result *notification_sendpay_failure(struct command *cmd, } assert(route->result == NULL); - route->result = tal_sendpay_result_from_json(route, buf, sub); + route->result = tal_sendpay_result_from_json(route, buf, sub, + route->shared_secrets); if (route->result == NULL) plugin_err(pay_plugin->plugin, "Unable to parse sendpay_failure: %.*s", @@ -449,7 +510,8 @@ struct command_result *notification_sendpay_success(struct command *cmd, } assert(route->result == NULL); - route->result = tal_sendpay_result_from_json(route, buf, sub); + route->result = tal_sendpay_result_from_json(route, buf, sub, + route->shared_secrets); if (route->result == NULL) plugin_err(pay_plugin->plugin, "Unable to parse sendpay_success: %.*s", diff --git a/plugins/renepay/sendpay.c b/plugins/renepay/sendpay.c new file mode 100644 index 000000000000..3c3049902147 --- /dev/null +++ b/plugins/renepay/sendpay.c @@ -0,0 +1,568 @@ +#include "config.h" +#include +#include +#include +#include +#include +#include + +static struct command_result *param_route_hops(struct command *cmd, + const char *name, + const char *buffer, + const jsmntok_t *tok, + struct route_hop **hops) +{ + size_t i; + const jsmntok_t *t; + const char *err; + + if (tok->type != JSMN_ARRAY) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "%s must be an array", name); + + *hops = tal_arr(cmd, struct route_hop, tok->size); + json_for_each_arr(i, t, tok) + { + struct amount_msat amount_msat; + struct node_id id; + struct short_channel_id channel; + unsigned delay, direction; + + err = json_scan(tmpctx, buffer, t, + "{amount_msat:%,id:%,channel:%,direction:%,delay:%}", + JSON_SCAN(json_to_msat, &amount_msat), + JSON_SCAN(json_to_node_id, &id), + JSON_SCAN(json_to_short_channel_id, &channel), + JSON_SCAN(json_to_number, &direction), + JSON_SCAN(json_to_number, &delay) + ); + if (err != NULL) { + return command_fail( + cmd, JSONRPC2_INVALID_PARAMS, + "Error parsing route_hop %s[%zu]: %s", name, i, + err); + } + + if (direction != 0 && direction != 1) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "direction must be either 0 or 1"); + + (*hops)[i].amount = amount_msat; + (*hops)[i].node_id = id; + (*hops)[i].delay = delay; + (*hops)[i].scid = channel; + (*hops)[i].direction = direction; + } + return NULL; +} + +static struct command_result *param_blinded_path(struct command *cmd, + const char *name, + const char *buffer, + const jsmntok_t *tok, + struct blinded_path **blinded_path) +{ + size_t i; + const jsmntok_t *t, *pathtok, *datatok; + const char *err; + + *blinded_path = tal(cmd, struct blinded_path); + err = json_scan( + tmpctx, buffer, tok, "{first_node_id:%,first_path_key:%}", + JSON_SCAN(json_to_pubkey, &(*blinded_path)->first_node_id.pubkey), + JSON_SCAN(json_to_pubkey, &(*blinded_path)->first_path_key)); + if (err != NULL) { + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Error parsing blinded_path %s: %s", name, + err); + } + pathtok = json_get_member(buffer, tok, "path"); + if (!pathtok) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "%s does not have a path", name); + if (pathtok->type != JSMN_ARRAY) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "path in %s must be an array", name); + + (*blinded_path)->path = + tal_arr(*blinded_path, struct blinded_path_hop *, pathtok->size); + json_for_each_arr(i, t, pathtok) + { + (*blinded_path)->path[i] = + tal((*blinded_path)->path, struct blinded_path_hop); + struct blinded_path_hop *hop = (*blinded_path)->path[i]; + + err = + json_scan(tmpctx, buffer, t, "{blinded_node_id:%}", + JSON_SCAN(json_to_pubkey, &hop->blinded_node_id)); + if (err != NULL) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Error parsing path[%zu]: %s", i, + err); + + datatok = + json_get_member(buffer, t, "encrypted_recipient_data"); + if (!datatok) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Error parsing path[%zu]: unable " + "to get encrypted_recipient_data", + i); + hop->encrypted_recipient_data = + json_tok_bin_from_hex(hop, buffer, datatok); + } + return NULL; +} + +struct renesendpay { + struct route_hop *route; + struct sha256 payment_hash; + u64 groupid, partid; + + u32 final_cltv; + struct amount_msat total_amount; + struct amount_msat deliver_amount; + struct amount_msat sent_amount; + struct node_id destination; + + struct secret *payment_secret; + struct blinded_path *blinded_path; + + const char *invoice, *label, *description; + const u8 *metadata; + + struct secret *shared_secrets; + unsigned int blockheight; +}; + +static struct command_result *rpc_fail(struct command *cmd, + const char *method, + const char *buffer, + const jsmntok_t *toks, + struct renesendpay *renesendpay) +{ + plugin_log(cmd->plugin, LOG_UNUSUAL, + "renesendpay failed calling %s: %.*s", method, + json_tok_full_len(toks), json_tok_full(buffer, toks)); + const jsmntok_t *codetok = json_get_member(buffer, toks, "code"); + const jsmntok_t *msgtok = json_get_member(buffer, toks, "message"); + const char *msg; + if (msgtok) + msg = json_strdup(tmpctx, buffer, msgtok); + else + msg = ""; + u32 errcode; + if (codetok != NULL) + json_to_u32(buffer, codetok, &errcode); + else + errcode = PLUGIN_ERROR; + struct json_stream *response = jsonrpc_stream_fail( + cmd, errcode, + tal_fmt(tmpctx, "%s failed: %s", method, msg ? msg : "\"\"")); + json_object_start(response, "data"); + json_add_sha256(response, "payment_hash", &renesendpay->payment_hash); + json_add_string(response, "status", "failed"); + json_add_amount_msat(response, "amount_sent_msat", + renesendpay->sent_amount); + json_object_end(response); + return command_finished(cmd, response); +} + +static void sphinx_append_blinded_path(const tal_t *ctx, + struct sphinx_path *sp, + const struct blinded_path *blinded_path, + const struct amount_msat deliver, + const struct amount_msat total, + const u32 final_cltv) +{ + const size_t pathlen = tal_count(blinded_path->path); + bool ret; + + for (size_t i = 0; i < pathlen; i++) { + bool first = (i == 0); + bool final = (i == pathlen - 1); + + const struct blinded_path_hop *bhop = blinded_path->path[i]; + const u8 *payload = onion_blinded_hop( + ctx, final ? &deliver : NULL, final ? &total : NULL, + final ? &final_cltv : NULL, bhop->encrypted_recipient_data, + first ? &blinded_path->first_path_key : NULL); + // FIXME: better handle error here + ret = sphinx_add_hop_has_length( + sp, + first ? &blinded_path->first_node_id.pubkey + : &bhop->blinded_node_id, + take(payload)); + assert(ret); + } +} + +static void sphinx_append_final_hop(const tal_t *ctx, + struct sphinx_path *sp, + const struct secret *payment_secret, + const struct node_id *node, + const struct amount_msat deliver, + const struct amount_msat total, + const u32 final_cltv, + const u8 *payment_metadata) +{ + struct pubkey destination; + bool ret = pubkey_from_node_id(&destination, node); + assert(ret); + + const u8 *payload = onion_final_hop(ctx, deliver, final_cltv, total, + payment_secret, payment_metadata); + // FIXME: better handle error here + ret = sphinx_add_hop_has_length(sp, &destination, take(payload)); + assert(ret); +} + +static const u8 *create_onion(const tal_t *ctx, + struct renesendpay *renesendpay, + const struct node_id first_node, + const size_t first_index) +{ + bool ret; + const tal_t *this_ctx = tal(ctx, tal_t); + struct node_id current_node = first_node; + struct pubkey node; + const u8 *payload; + const size_t pathlen = tal_count(renesendpay->route); + + struct sphinx_path *sp = + sphinx_path_new(this_ctx, renesendpay->payment_hash.u.u8, + sizeof(renesendpay->payment_hash.u.u8)); + + for (size_t i = first_index; i < pathlen; i++) { + /* Encrypted message is for node[i] but the data is hop[i+1], + * therein lays the problem with sendpay's API. */ + ret = pubkey_from_node_id(&node, ¤t_node); + assert(ret); + + struct route_hop *hop = &renesendpay->route[i]; + payload = + onion_nonfinal_hop(this_ctx, &hop->scid, hop->amount, + hop->delay + renesendpay->blockheight); + // FIXME: better handle error here + ret = sphinx_add_hop_has_length(sp, &node, take(payload)); + assert(ret); + current_node = renesendpay->route[i].node_id; + } + + const u32 final_cltv = renesendpay->final_cltv + renesendpay->blockheight; + if(renesendpay->blinded_path){ + sphinx_append_blinded_path(this_ctx, + sp, + renesendpay->blinded_path, + renesendpay->deliver_amount, + renesendpay->total_amount, + final_cltv); + }else{ + sphinx_append_final_hop(this_ctx, + sp, + renesendpay->payment_secret, + ¤t_node, + renesendpay->deliver_amount, + renesendpay->total_amount, + final_cltv, + renesendpay->metadata); + } + + struct secret *shared_secrets; + struct onionpacket *packet = create_onionpacket( + this_ctx, sp, ROUTING_INFO_SIZE, &shared_secrets); + renesendpay->shared_secrets = tal_steal(renesendpay, shared_secrets); + + const u8 *onion = serialize_onionpacket(ctx, packet); + tal_free(this_ctx); + return onion; +} + +static struct command_result *renesendpay_done(struct command *cmd, + const char *method UNUSED, + const char *buffer, + const jsmntok_t *toks, + struct renesendpay *renesendpay) +{ + const char *err; + u64 created_index; + u32 timestamp; + err = json_scan(tmpctx, buffer, toks, "{created_index:%,created_at:%}", + JSON_SCAN(json_to_u64, &created_index), + JSON_SCAN(json_to_u32, ×tamp)); + if (err) + return command_fail( + cmd, JSONRPC2_INVALID_PARAMS, + "renesendpay failed to read response from sendonion: %s", + err); + + struct json_stream *response = jsonrpc_stream_success(cmd); + json_add_string(response, "message", + "Monitor status with listpays or waitsendpay"); + + json_add_u64(response, "created_index", created_index); + json_add_u32(response, "created_at", timestamp); + json_add_sha256(response, "payment_hash", &renesendpay->payment_hash); + json_add_u64(response, "groupid", renesendpay->groupid); + json_add_u64(response, "partid", renesendpay->partid); + json_add_node_id(response, "destination", &renesendpay->destination); + json_add_amount_msat(response, "amount_sent_msat", + renesendpay->sent_amount); + json_add_amount_msat(response, "amount_delivered_msat", + renesendpay->deliver_amount); + json_add_amount_msat(response, "amount_total_msat", + renesendpay->total_amount); + json_add_string(response, "invoice", renesendpay->invoice); + json_add_string(response, "status", "pending"); + + const jsmntok_t *preimagetok = + json_get_member(buffer, toks, "payment_preimage"); + if (preimagetok) + json_add_tok(response, "payment_preimage", preimagetok, buffer); + + if (renesendpay->label) + json_add_string(response, "label", renesendpay->label); + if (renesendpay->description) + json_add_string(response, "description", + renesendpay->description); + if (renesendpay->metadata) + json_add_hex_talarr(response, "payment_metadata", + renesendpay->metadata); + + if (renesendpay->shared_secrets) { + json_array_start(response, "shared_secrets"); + for (size_t i = 0; i < tal_count(renesendpay->shared_secrets); + i++) { + json_add_secret(response, NULL, + &renesendpay->shared_secrets[i]); + } + json_array_end(response); + } + + /* FIXME: shall we report the blinded path, secret and route used? */ + return command_finished(cmd, response); +} + +static struct command_result *renesendpay_finished(struct command *cmd, + struct renesendpay *renesendpay) +{ + struct json_stream *response = jsonrpc_stream_success(cmd); + json_add_string(response, "message", + "Monitor status with listpays or waitsendpay"); + json_add_sha256(response, "payment_hash", &renesendpay->payment_hash); + json_add_u64(response, "groupid", renesendpay->groupid); + json_add_u64(response, "partid", renesendpay->partid); + json_add_node_id(response, "destination", &renesendpay->destination); + json_add_amount_msat(response, "amount_sent_msat", + renesendpay->sent_amount); + json_add_amount_msat(response, "amount_delivered_msat", + renesendpay->deliver_amount); + json_add_amount_msat(response, "amount_total_msat", + renesendpay->total_amount); + json_add_string(response, "invoice", renesendpay->invoice); + json_add_string(response, "status", "pending"); + + if (renesendpay->label) + json_add_string(response, "label", renesendpay->label); + if (renesendpay->description) + json_add_string(response, "description", + renesendpay->description); + if (renesendpay->metadata) + json_add_hex_talarr(response, "payment_metadata", + renesendpay->metadata); + + if (renesendpay->shared_secrets) { + json_array_start(response, "shared_secrets"); + for (size_t i = 0; i < tal_count(renesendpay->shared_secrets); + i++) { + json_add_secret(response, NULL, + &renesendpay->shared_secrets[i]); + } + json_array_end(response); + } + return command_finished(cmd, response); +} + +static u32 initial_cltv_delta(const struct renesendpay *renesendpay) +{ + if (tal_count(renesendpay->route) == 0) + return renesendpay->final_cltv; + return renesendpay->route[0].delay; +} + +static struct command_result *waitblockheight_done(struct command *cmd, + const char *method UNUSED, + const char *buffer, + const jsmntok_t *toks, + struct renesendpay *renesendpay) +{ + const char *err; + err = json_scan(tmpctx, buffer, toks, "{blockheight:%}", + JSON_SCAN(json_to_u32, &renesendpay->blockheight)); + renesendpay->blockheight += 1; + if (err) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "renesendpay failed to read blockheight " + "from waitblockheight response."); + + const u8 *onion; + struct out_req *req; + + if (tal_count(renesendpay->route) > 0) { + onion = create_onion(tmpctx, renesendpay, + renesendpay->route[0].node_id, 1); + req = jsonrpc_request_start(cmd, "sendonion", renesendpay_done, + rpc_fail, renesendpay); + json_add_hex_talarr(req->js, "onion", onion); + + json_add_amount_msat(req->js, "amount_msat", + renesendpay->deliver_amount); + + const struct route_hop *hop = &renesendpay->route[0]; + json_object_start(req->js, "first_hop"); + json_add_amount_msat(req->js, "amount_msat", hop->amount); + json_add_num(req->js, "delay", + hop->delay + renesendpay->blockheight); + json_add_node_id(req->js, "id", &hop->node_id); + json_add_short_channel_id(req->js, "channel", hop->scid); + json_object_end(req->js); + + json_array_start(req->js, "shared_secrets"); + for (size_t i = 0; i < tal_count(renesendpay->shared_secrets); + i++) { + json_add_secret(req->js, NULL, + &renesendpay->shared_secrets[i]); + } + json_array_end(req->js); + + json_add_node_id(req->js, "destination", + &renesendpay->destination); + json_add_sha256(req->js, "payment_hash", + &renesendpay->payment_hash); + json_add_u64(req->js, "partid", renesendpay->partid); + json_add_u64(req->js, "groupid", renesendpay->groupid); + if (renesendpay->label) + json_add_string(req->js, "label", renesendpay->label); + if (renesendpay->description) + json_add_string(req->js, "description", + renesendpay->description); + if (renesendpay->invoice) + json_add_string(req->js, "bolt11", + renesendpay->invoice); + } else { + /* This is either a self-payment or a payment through a blinded + * path that starts at our node. */ + // FIXME: do this with injectpaymentonion. + // FIXME: we could make all payments with injectpaymentonion but + // we need to make sure first that we don't lose older features, + // like for example to be able to show in listsendpays the + // recepient of the payment. + onion = create_onion(tmpctx, renesendpay, pay_plugin->my_id, 0); + req = jsonrpc_request_start(cmd, "injectpaymentonion", + renesendpay_done, rpc_fail, + renesendpay); + + json_add_hex_talarr(req->js, "onion", onion); + json_add_sha256(req->js, "payment_hash", + &renesendpay->payment_hash); + json_add_u64(req->js, "partid", renesendpay->partid); + json_add_u64(req->js, "groupid", renesendpay->groupid); + if (renesendpay->label) + json_add_string(req->js, "label", renesendpay->label); + if (renesendpay->invoice) + json_add_string(req->js, "invstring", + renesendpay->invoice); + json_add_amount_msat(req->js, "amount_msat", + renesendpay->sent_amount); + json_add_amount_msat(req->js, "destination_msat", + renesendpay->deliver_amount); + json_add_u32(req->js, "cltv_expiry", + initial_cltv_delta(renesendpay) + + renesendpay->blockheight); + } + send_outreq(req); + return renesendpay_finished(cmd, renesendpay); +} + +struct command_result *json_renesendpay(struct command *cmd, + const char *buf, + const jsmntok_t *params) +{ + struct route_hop *route; + struct sha256 *payment_hash; + const char *invoice, *label, *description; + struct amount_msat *amount, *total_amount; + u64 *groupid, *partid; + u32 *final_cltv; + struct node_id *destination; + u8 *metadata; + + /* only used in the case of BOLT11 */ + struct secret *payment_secret; + + /* only used in the case of BOLT12 */ + struct blinded_path *blinded_path; + + if (!param(cmd, buf, params, + p_req("route", param_route_hops, &route), + p_req("payment_hash", param_sha256, &payment_hash), + p_req("groupid", param_u64, &groupid), + p_req("partid", param_u64, &partid), + p_req("amount_msat", param_msat, &amount), + p_req("total_amount_msat", param_msat, &total_amount), + p_req("destination", param_node_id, &destination), + p_req("final_cltv", param_u32, &final_cltv), + p_opt("payment_secret", param_secret, &payment_secret), + p_opt("blinded_path", param_blinded_path, &blinded_path), + p_opt("invoice", param_invstring, &invoice), + p_opt("label", param_string, &label), + p_opt("description", param_string, &description), + p_opt("metadata", param_bin_from_hex, &metadata), + NULL)) + return command_param_failed(); + + if (payment_secret && blinded_path) + return command_fail( + cmd, JSONRPC2_INVALID_PARAMS, + "A payment cannot have both a secret and a blinded path."); + if (!payment_secret && !blinded_path) + return command_fail( + cmd, JSONRPC2_INVALID_PARAMS, + "For a BOLT11 payment a payment_secret " + "must be specified and for a BOLT12 " + "payment a blinded_path must be specified."); + + plugin_log(cmd->plugin, LOG_DBG, "renesendpay called: %.*s", + json_tok_full_len(params), json_tok_full(buf, params)); + + struct renesendpay *renesendpay = tal(cmd, struct renesendpay); + renesendpay->route = tal_steal(renesendpay, route); + renesendpay->payment_hash = *payment_hash; + renesendpay->partid = *partid; + renesendpay->groupid = *groupid; + + if (tal_count(renesendpay->route) > 0) + renesendpay->sent_amount = renesendpay->route[0].amount; + else /* this might be a self pay */ + renesendpay->sent_amount = *amount; + + renesendpay->total_amount = *total_amount; + renesendpay->deliver_amount = *amount; + renesendpay->final_cltv = *final_cltv; + + renesendpay->destination = *destination; + + renesendpay->payment_secret = tal_steal(renesendpay, payment_secret); + renesendpay->blinded_path = tal_steal(renesendpay, blinded_path); + + renesendpay->invoice = tal_steal(renesendpay, invoice); + renesendpay->label = tal_steal(renesendpay, label); + renesendpay->description = tal_steal(renesendpay, description); + renesendpay->metadata = tal_steal(renesendpay, metadata); + renesendpay->shared_secrets = NULL; + + struct out_req *req = + jsonrpc_request_start(cmd, "waitblockheight", waitblockheight_done, + rpc_fail, renesendpay); + json_add_num(req->js, "blockheight", 0); + return send_outreq(req); +} diff --git a/plugins/renepay/sendpay.h b/plugins/renepay/sendpay.h new file mode 100644 index 000000000000..1012063368fe --- /dev/null +++ b/plugins/renepay/sendpay.h @@ -0,0 +1,10 @@ +#ifndef LIGHTNING_PLUGINS_RENEPAY_SENDPAY_H +#define LIGHTNING_PLUGINS_RENEPAY_SENDPAY_H + +#include "config.h" + +struct command_result *json_renesendpay(struct command *cmd, + const char *buf, + const jsmntok_t *params); + +#endif /* LIGHTNING_PLUGINS_RENEPAY_SENDPAY_H */ diff --git a/plugins/renepay/test/run-bottleneck.c b/plugins/renepay/test/run-bottleneck.c index 9aa62115aebb..f22d45fbbf6e 100644 --- a/plugins/renepay/test/run-bottleneck.c +++ b/plugins/renepay/test/run-bottleneck.c @@ -263,6 +263,7 @@ int main(int argc, char *argv[]) /* feebudget */maxfee, &next_partid, groupid, + false, &errcode, &err_msg); diff --git a/plugins/renepay/test/run-testflow.c b/plugins/renepay/test/run-testflow.c index ffed14f7e55a..83ffe80e3766 100644 --- a/plugins/renepay/test/run-testflow.c +++ b/plugins/renepay/test/run-testflow.c @@ -691,7 +691,7 @@ static void test_flow_to_route(void) F->dirs[0]=0; deliver = AMOUNT_MSAT(250000000); F->amount = deliver; - route = flow_to_route(this_ctx, 1, 1, payment_hash, 0, gossmap, F); + route = flow_to_route(this_ctx, 1, 1, payment_hash, 0, gossmap, F, false); assert(route); assert(amount_msat_eq(route->hops[0].amount, deliver)); @@ -707,7 +707,7 @@ static void test_flow_to_route(void) F->dirs[1]=0; deliver = AMOUNT_MSAT(250000000); F->amount=deliver; - route = flow_to_route(this_ctx, 1, 1, payment_hash, 0, gossmap, F); + route = flow_to_route(this_ctx, 1, 1, payment_hash, 0, gossmap, F, false); assert(route); assert(amount_msat_eq(route->hops[0].amount, amount_msat(250050016))); @@ -725,7 +725,7 @@ static void test_flow_to_route(void) F->dirs[2]=0; deliver = AMOUNT_MSAT(250000000); F->amount=deliver; - route = flow_to_route(this_ctx, 1, 1, payment_hash, 0, gossmap, F); + route = flow_to_route(this_ctx, 1, 1, payment_hash, 0, gossmap, F, false); assert(route); assert(amount_msat_eq(route->hops[0].amount, amount_msat(250087534))); @@ -745,7 +745,7 @@ static void test_flow_to_route(void) F->dirs[3]=0; deliver = AMOUNT_MSAT(250000000); F->amount=deliver; - route = flow_to_route(this_ctx, 1, 1, payment_hash, 0, gossmap, F); + route = flow_to_route(this_ctx, 1, 1, payment_hash, 0, gossmap, F, false); assert(route); assert(amount_msat_eq(route->hops[0].amount, amount_msat(250112544))); diff --git a/plugins/renepay/uncertainty.c b/plugins/renepay/uncertainty.c index d151471229a0..15ca19288aef 100644 --- a/plugins/renepay/uncertainty.c +++ b/plugins/renepay/uncertainty.c @@ -147,12 +147,12 @@ uncertainty_get_chan_extra_map(struct uncertainty *uncertainty) } /* Add channel to the Uncertainty Network if it doesn't already exist. */ -const struct chan_extra * +struct chan_extra * uncertainty_add_channel(struct uncertainty *uncertainty, const struct short_channel_id scid, struct amount_msat capacity) { - const struct chan_extra *ce = + struct chan_extra *ce = chan_extra_map_get(uncertainty->chan_extra_map, scid); if (ce) return ce; diff --git a/plugins/renepay/uncertainty.h b/plugins/renepay/uncertainty.h index 894a95732b49..8ae65eab6c99 100644 --- a/plugins/renepay/uncertainty.h +++ b/plugins/renepay/uncertainty.h @@ -43,7 +43,7 @@ struct uncertainty *uncertainty_new(const tal_t *ctx); struct chan_extra_map * uncertainty_get_chan_extra_map(struct uncertainty *uncertainty); -const struct chan_extra * +struct chan_extra * uncertainty_add_channel(struct uncertainty *uncertainty, const struct short_channel_id scid, struct amount_msat capacity); diff --git a/tests/test_renepay.py b/tests/test_renepay.py index 2c5221eeec8f..a9c12efd27cc 100644 --- a/tests/test_renepay.py +++ b/tests/test_renepay.py @@ -110,7 +110,7 @@ def test_errors(node_factory, bitcoind): node_factory.join_nodes([l1, l2, l4], wait_for_announce=True, fundamount=1000000) node_factory.join_nodes([l1, l3, l5], wait_for_announce=True, fundamount=1000000) - failmsg = r"Destination is unknown in the network gossip." + failmsg = r"failed to find a feasible flow" with pytest.raises(RpcError, match=failmsg): l1.rpc.call("renepay", {"invstring": inv}) @@ -140,7 +140,7 @@ def test_errors(node_factory, bitcoind): PAY_DESTINATION_PERM_FAIL = 203 assert err.value.error["code"] == PAY_DESTINATION_PERM_FAIL - assert "WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS" in err.value.error["message"] + assert "Unknown invoice" in err.value.error["message"] @pytest.mark.openchannel("v1") @@ -509,6 +509,7 @@ def test_htlc_max(node_factory): assert invoice["amount_received_msat"] >= Millisatoshi("800000sat") +@unittest.skip def test_previous_sendpays(node_factory, bitcoind): """ Check that renepay can complete a payment that already started @@ -839,3 +840,34 @@ def test_description(node_factory): "renepay", {"invstring": inv_with_hash, "description": "paying for coffee"} ) assert details["status"] == "complete" + + +def test_offers(node_factory): + l1, l2, l3 = node_factory.line_graph(3, wait_for_announce=True) + offer = l3.rpc.offer("1000sat", "test_renepay_offers")['bolt12'] + invoice = l1.rpc.fetchinvoice(offer)['invoice'] + response = l1.rpc.call("renepay", {"invstring": invoice}) + assert response["status"] == "complete" + + +def test_offer_selfpay(node_factory): + """We can fetch an pay our own offer""" + l1 = node_factory.get_node() + offer = l1.rpc.offer(amount="2msat", description="test_offer_path_self")["bolt12"] + inv = l1.rpc.fetchinvoice(offer)["invoice"] + l1.rpc.call("renepay", {"invstring": inv}) + + +def test_unannounced(node_factory): + l1, l2 = node_factory.line_graph(2, announce_channels=False) + # BOLT11 direct peer + b11 = l2.rpc.invoice( + "100sat", "test_renepay_unannounced", "test_renepay_unannounced" + )["bolt11"] + ret = l1.rpc.call("renepay", {"invstring": b11}) + assert ret["status"] == "complete" + # BOLT12 direct peer + offer = l2.rpc.offer("any")["bolt12"] + b12 = l1.rpc.fetchinvoice(offer, "21sat")["invoice"] + ret = l1.rpc.call("renepay", {"invstring": b12}) + assert ret["status"] == "complete"