Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

renepay: Support for BOLT12 #7985

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion plugins/renepay/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand All @@ -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 \
Expand All @@ -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
2 changes: 1 addition & 1 deletion plugins/renepay/json.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,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;
Expand Down
215 changes: 148 additions & 67 deletions plugins/renepay/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <plugins/renepay/mods.h>
#include <plugins/renepay/payplugin.h>
#include <plugins/renepay/routetracker.h>
#include <plugins/renepay/sendpay.h>
#include <stdio.h>

// TODO(eduardo): notice that pending attempts performed with another
Expand Down Expand Up @@ -168,6 +169,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;
u32 *maxdelay;
u32 *retryfor;
const char *description;
Expand All @@ -188,6 +190,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),
Expand All @@ -205,9 +210,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
Expand Down Expand Up @@ -236,38 +238,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;
struct tlv_invoice *b12;

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*
Expand All @@ -277,46 +297,106 @@ 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;
payment->routing_destination = &pinfo->destination;
} 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. */
payment->routing_destination =
tal(payment, struct node_id);
if (!node_id_from_hexstr(
"02""0000000000000000000000000000000000000000000000000000000000000001",
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");
Expand All @@ -337,20 +417,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");
Expand Down Expand Up @@ -382,6 +459,10 @@ static const struct plugin_command commands[] = {
"renepay",
json_pay
},
{
"renesendpay",
json_renesendpay
},
};

static const struct plugin_notification notifications[] = {
Expand Down
Loading
Loading