Skip to content
Open
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: 4 additions & 0 deletions doc/lightningd-config.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,10 @@ delete the others.

### Payment and invoice control options:

* **payment-fronting-node**=*nodeid*

Always use this *nodeid* as the entry point when we generate invoices or offers. For BOLT11 invoices, this node must be a neighbor: we will use a routehint with the alias for the short channel id to provide limited privacy (we still reveal our node id). For BOLT12 invoices and offers , we provide a blinded path from the node to us, to provide better privacy.

* **disable-mpp** [plugin `pay`]

Disable the multi-part payment sending support in the `pay` plugin. By default
Expand Down
25 changes: 24 additions & 1 deletion lightningd/invoice.c
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,25 @@ static struct route_info **select_inchan_mpp(const tal_t *ctx,
return routehints;
}

static struct route_info **select_inchan_all(const tal_t *ctx,
struct lightningd *ld,
struct routehint_candidate
*candidates)
{
struct route_info **routehints;

log_debug(ld->log, "Selecting all %zu candidates",
tal_count(candidates));

routehints = tal_arr(ctx, struct route_info *, tal_count(candidates));
for (size_t i = 0; i < tal_count(candidates); i++) {
routehints[i] = tal_dup(routehints, struct route_info,
candidates[i].r);
}

return routehints;
}

/* Encapsulating struct while we wait for gossipd to give us incoming channels */
struct chanhints {
bool expose_all_private;
Expand Down Expand Up @@ -723,6 +742,10 @@ add_routehints(struct invoice_info *info,

needed = info->b11->msat ? *info->b11->msat : AMOUNT_MSAT(1);

/* --payment-fronting-node means use all candidates. */
if (tal_count(info->cmd->ld->fronting_nodes))
info->b11->routes = select_inchan_all(info->b11, info->cmd->ld, candidates);

/* If we are not completely unpublished, try with reservoir
* sampling first.
*
Expand All @@ -738,7 +761,7 @@ add_routehints(struct invoice_info *info,
* should make an effort to avoid overlapping incoming
* channels, which is done by select_inchan_mpp.
*/
if (!node_unpublished)
else if (!node_unpublished)
info->b11->routes = select_inchan(info->b11,
info->cmd->ld,
needed,
Expand Down
1 change: 1 addition & 0 deletions lightningd/lightningd.c
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@ static struct lightningd *new_lightningd(const tal_t *ctx)
/* The gossip seeker automatically connects to a this many peers */
ld->autoconnect_seeker_peers = 10;

ld->fronting_nodes = tal_arr(ld, struct node_id, 0);
return ld;
}

Expand Down
3 changes: 3 additions & 0 deletions lightningd/lightningd.h
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,9 @@ struct lightningd {

/* Minimum number of peers seeker should maintain. */
u32 autoconnect_seeker_peers;

/* Nodes to use for invoices / offers */
struct node_id *fronting_nodes;
};

/* Turning this on allows a tal allocation to return NULL, rather than aborting.
Expand Down
15 changes: 15 additions & 0 deletions lightningd/options.c
Original file line number Diff line number Diff line change
Expand Up @@ -1278,6 +1278,16 @@ static char *opt_add_api_beg(const char *arg, struct lightningd *ld)
return NULL;
}

static char *opt_add_node_id(const char *arg, struct node_id **arr)
{
struct node_id n;
if (!node_id_from_hexstr(arg, strlen(arg), &n))
return "Unparsable nodeid";

tal_arr_expand(arr, n);
return NULL;
}

char *hsm_secret_arg(const tal_t *ctx,
const char *arg,
const u8 **hsm_secret)
Expand Down Expand Up @@ -1598,6 +1608,10 @@ static void register_opts(struct lightningd *ld)
ld,
"Re-enable a long-deprecated API (which will be removed entirely next version!)");
opt_register_logging(ld);
clnopt_witharg("--payment-fronting-node",
OPT_MULTI,
opt_add_node_id, NULL,
&ld->fronting_nodes, "Put this node in all invoices and offers, and use blinded path (bolt12) or route hints (bolt11) to route to this node. Must be a neighboring node. Can be specified multiple times.");

dev_register_opts(ld);
}
Expand Down Expand Up @@ -1854,6 +1868,7 @@ bool is_known_opt_cb_arg(char *(*cb_arg)(const char *, void *))
|| cb_arg == (void *)opt_subd_dev_disconnect
|| cb_arg == (void *)opt_set_crash_timeout
|| cb_arg == (void *)opt_add_api_beg
|| cb_arg == (void *)opt_add_node_id
|| cb_arg == (void *)opt_force_featureset
|| cb_arg == (void *)opt_force_privkey
|| cb_arg == (void *)opt_force_bip32_seed
Expand Down
8 changes: 2 additions & 6 deletions lightningd/plugin.c
Original file line number Diff line number Diff line change
Expand Up @@ -2124,12 +2124,8 @@ void plugins_init(struct plugins *plugins)
setenv("LIGHTNINGD_PLUGIN", "1", 1);
setenv("LIGHTNINGD_VERSION", version(), 1);

if (plugins_send_getmanifest(plugins, NULL)) {
void *ret;
ret = io_loop_with_timers(plugins->ld);
log_debug(plugins->ld->log, "io_loop_with_timers: %s", __func__);
assert(ret == plugins);
}
if (plugins_send_getmanifest(plugins, NULL))
io_loop_with_timers(plugins->ld);
}

static void plugin_config_cb(const char *buffer,
Expand Down
33 changes: 29 additions & 4 deletions lightningd/routehint.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ static bool scid_in_arr(const struct short_channel_id *scidarr,
return false;
}

static bool is_fronting_node(const struct lightningd *ld,
const struct node_id *node)
{
for (size_t i = 0; i < tal_count(ld->fronting_nodes); i++) {
if (node_id_eq(&ld->fronting_nodes[i], node))
return true;
}
return false;
}

struct routehint_candidate *
routehint_candidates(const tal_t *ctx,
struct lightningd *ld,
Expand Down Expand Up @@ -62,7 +72,7 @@ routehint_candidates(const tal_t *ctx,
struct routehint_candidate candidate;
struct amount_msat fee_base, htlc_max;
struct route_info *r;
bool is_public;
bool is_public, is_fronting;

r = tal(tmpctx, struct route_info);

Expand Down Expand Up @@ -92,6 +102,17 @@ routehint_candidates(const tal_t *ctx,
json_tok_full(buf, toks));
}

/* If they specify fronting nodes, always use them. */
if (tal_count(ld->fronting_nodes)) {
if (!is_fronting_node(ld, &r->pubkey)) {
log_debug(ld->log, "%s: not a fronting node",
fmt_node_id(tmpctx, &r->pubkey));
continue;
}
is_fronting = true;
} else
is_fronting = false;

/* Note: listincoming returns real scid or local alias if no real scid. */
candidate.c = any_channel_by_scid(ld, r->short_channel_id, true);
if (!candidate.c) {
Expand Down Expand Up @@ -133,6 +154,10 @@ routehint_candidates(const tal_t *ctx,
if (expose_all_private != NULL && *expose_all_private)
is_public = true;

/* Also, consider fronting nodes public */
if (is_fronting)
is_public = true;

r->fee_base_msat = fee_base.millisatoshis; /* Raw: route_info */
/* Could wrap: if so ignore */
if (!amount_msat_eq(amount_msat(r->fee_base_msat), fee_base)) {
Expand All @@ -156,7 +181,7 @@ routehint_candidates(const tal_t *ctx,
continue;
}
/* If they give us a hint, we use even if capacity 0 */
} else if (amount_msat_is_zero(capacity)) {
} else if (!is_fronting && amount_msat_is_zero(capacity)) {
log_debug(ld->log, "%s: deadend",
fmt_short_channel_id(tmpctx,
r->short_channel_id));
Expand All @@ -166,8 +191,8 @@ routehint_candidates(const tal_t *ctx,
continue;
}

/* Is it offline? */
if (candidate.c->owner == NULL) {
/* Is it offline? Leave it if it's fronting. */
if (!is_fronting && candidate.c->owner == NULL) {
log_debug(ld->log, "%s: offline",
fmt_short_channel_id(tmpctx,
r->short_channel_id));
Expand Down
20 changes: 12 additions & 8 deletions plugins/fetchinvoice.c
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,7 @@ static struct command_result *establish_path_fail(struct command *cmd,
static struct command_result *try_establish(struct command *cmd,
struct establishing_paths *epaths)
{
const struct offers_data *od = get_offers_data(cmd->plugin);
struct pubkey target;

if (epaths->sent->direct_dest) {
Expand All @@ -596,8 +597,8 @@ static struct command_result *try_establish(struct command *cmd,
epaths->sent->issuer_key = &bpath->path[tal_count(bpath->path)-1]->blinded_node_id;
}

return establish_onion_path(cmd, get_gossmap(cmd->plugin), &id, &target,
disable_connect,
return establish_onion_path(cmd, get_gossmap(cmd->plugin), &od->id, &target,
od->disable_connect,
establish_path_done,
establish_path_fail,
epaths);
Expand Down Expand Up @@ -799,15 +800,16 @@ static struct command_result *param_dev_reply_path(struct command *cmd, const ch
return NULL;
}

static bool payer_key(const u8 *public_tweak, size_t public_tweak_len,
static bool payer_key(const struct offers_data *od,
const u8 *public_tweak, size_t public_tweak_len,
struct pubkey *key)
{
struct sha256 tweakhash;

bolt12_alias_tweak(&nodealias_base, public_tweak, public_tweak_len,
bolt12_alias_tweak(&od->nodealias_base, public_tweak, public_tweak_len,
&tweakhash);

*key = id;
*key = od->id;
return secp256k1_ec_pubkey_tweak_add(secp256k1_ctx,
&key->pubkey,
tweakhash.u.u8) == 1;
Expand All @@ -818,6 +820,7 @@ struct command_result *json_fetchinvoice(struct command *cmd,
const char *buffer,
const jsmntok_t *params)
{
const struct offers_data *od = get_offers_data(cmd->plugin);
struct amount_msat *msat;
const char *rec_label, *payer_note;
u8 *payer_metadata;
Expand Down Expand Up @@ -988,7 +991,7 @@ struct command_result *json_fetchinvoice(struct command *cmd,
rec_label,
strlen(rec_label));

bolt12_alias_tweak(&nodealias_base,
bolt12_alias_tweak(&od->nodealias_base,
tweak_input,
tal_bytelen(tweak_input),
&tweak);
Expand Down Expand Up @@ -1054,7 +1057,7 @@ struct command_result *json_fetchinvoice(struct command *cmd,

/* We derive transient payer_id from invreq_metadata */
invreq->invreq_payer_id = tal(invreq, struct pubkey);
if (!payer_key(invreq->invreq_metadata,
if (!payer_key(od, invreq->invreq_metadata,
tal_bytelen(invreq->invreq_metadata),
invreq->invreq_payer_id)) {
/* Doesn't happen! */
Expand Down Expand Up @@ -1345,6 +1348,7 @@ struct command_result *json_sendinvoice(struct command *cmd,
const char *buffer,
const jsmntok_t *params)
{
const struct offers_data *od = get_offers_data(cmd->plugin);
struct amount_msat *msat;
u32 *timeout;
struct sent *sent = tal(cmd, struct sent);
Expand Down Expand Up @@ -1417,7 +1421,7 @@ struct command_result *json_sendinvoice(struct command *cmd,
* - MUST set `invoice_node_id` to the final `blinded_node_id` on the path it received the invoice request
*/
sent->inv->invoice_node_id = tal(sent->inv, struct pubkey);
sent->inv->invoice_node_id->pubkey = id.pubkey;
sent->inv->invoice_node_id->pubkey = od->id.pubkey;

/* BOLT #12:
* - if the expiry for accepting payment is not 7200 seconds
Expand Down
20 changes: 17 additions & 3 deletions plugins/libplugin.c
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ struct plugin_option {
const char *depr_start, *depr_end;
/* If true, allow setting after plugin has initialized */
bool dynamic;
/* If true, allow multiple settings. */
bool multi;
};

struct plugin {
Expand Down Expand Up @@ -1262,6 +1264,7 @@ handle_getmanifest(struct command *getmanifest_cmd,
json_add_string(params, "description", p->opts[i].description);
json_add_deprecated(params, "deprecated", p->opts[i].depr_start, p->opts[i].depr_end);
json_add_bool(params, "dynamic", p->opts[i].dynamic);
json_add_bool(params, "multi", p->opts[i].multi);
if (p->opts[i].jsonfmt)
p->opts[i].jsonfmt(p, params, "default", p->opts[i].arg);
json_object_end(params);
Expand Down Expand Up @@ -1598,9 +1601,19 @@ static struct command_result *handle_init(struct command *cmd,
if (!popt)
plugin_err(p, "lightningd specified unknown option '%s'?", name);

problem = popt->handle(p, json_strdup(tmpctx, buf, t+1), false, popt->arg);
if (problem)
plugin_err(p, "option '%s': %s", popt->name, problem);
if (popt->multi) {
size_t j;
const jsmntok_t *opt;
json_for_each_arr(j, opt, t+1) {
problem = popt->handle(p, json_strdup(tmpctx, buf, opt), false, popt->arg);
if (problem)
plugin_err(p, "option '%s': %s", popt->name, problem);
}
} else {
problem = popt->handle(p, json_strdup(tmpctx, buf, t+1), false, popt->arg);
if (problem)
plugin_err(p, "option '%s': %s", popt->name, problem);
}
}

if (p->init) {
Expand Down Expand Up @@ -2449,6 +2462,7 @@ static struct plugin *new_plugin(const tal_t *ctx,
o.depr_start = va_arg(ap, const char *);
o.depr_end = va_arg(ap, const char *);
o.dynamic = va_arg(ap, int); /* bool gets promoted! */
o.multi = va_arg(ap, int); /* bool gets promoted! */
tal_arr_expand(&p->opts, o);
}

Expand Down
18 changes: 11 additions & 7 deletions plugins/libplugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,7 @@ void *plugin_get_data_(struct plugin *plugin);
#define plugin_get_data(plugin, type) ((type *)(plugin_get_data_(plugin)))

/* Macro to define arguments */
#define plugin_option_(name, type, description, set, jsonfmt, arg, dev_only, depr_start, depr_end, dynamic) \
#define plugin_option_(name, type, description, set, jsonfmt, arg, dev_only, depr_start, depr_end, dynamic, multi) \
(name), \
(type), \
(description), \
Expand All @@ -594,23 +594,27 @@ void *plugin_get_data_(struct plugin *plugin);
(dev_only), \
(depr_start), \
(depr_end), \
(dynamic)
(dynamic), \
(multi)

/* jsonfmt can be NULL, but then default won't be printed */
#define plugin_option(name, type, description, set, jsonfmt, arg) \
plugin_option_((name), (type), (description), (set), (jsonfmt), (arg), false, NULL, NULL, false)
plugin_option_((name), (type), (description), (set), (jsonfmt), (arg), false, NULL, NULL, false, false)

#define plugin_option_dev(name, type, description, set, jsonfmt, arg) \
plugin_option_((name), (type), (description), (set), (jsonfmt), (arg), true, NULL, NULL, false)
plugin_option_((name), (type), (description), (set), (jsonfmt), (arg), true, NULL, NULL, false, false)

#define plugin_option_dev_dynamic(name, type, description, set, jsonfmt, arg) \
plugin_option_((name), (type), (description), (set), (jsonfmt), (arg), true, NULL, NULL, true)
plugin_option_((name), (type), (description), (set), (jsonfmt), (arg), true, NULL, NULL, true, false)

#define plugin_option_dynamic(name, type, description, set, jsonfmt, arg) \
plugin_option_((name), (type), (description), (set), (jsonfmt), (arg), false, NULL, NULL, true)
plugin_option_((name), (type), (description), (set), (jsonfmt), (arg), false, NULL, NULL, true, false)

#define plugin_option_deprecated(name, type, description, depr_start, depr_end, set, jsonfmt, arg) \
plugin_option_((name), (type), (description), (set), (jsonfmt), (arg), false, (depr_start), (depr_end), false)
plugin_option_((name), (type), (description), (set), (jsonfmt), (arg), false, (depr_start), (depr_end), false, false)

#define plugin_option_multi(name, type, description, set, jsonfmt, arg) \
plugin_option_((name), (type), (description), (set), (jsonfmt), (arg), false, NULL, NULL, false, true)

/* Standard helpers */
char *u64_option(struct plugin *plugin, const char *arg, bool check_only, u64 *i);
Expand Down
Loading
Loading