Skip to content

Commit 5a350a5

Browse files
committed
lightningd: honor payment-fronting-node when making bolt12 offers.
We use all the fronting nodes when creating offers. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
1 parent 31105a0 commit 5a350a5

File tree

5 files changed

+122
-36
lines changed

5 files changed

+122
-36
lines changed

plugins/offers.c

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ bool we_want_blinded_path(struct plugin *plugin, bool for_payment)
7676
u8 rgb_color[3], alias[32];
7777
struct tlv_node_ann_tlvs *na_tlvs;
7878

79+
/* If we're fronting, we always want a blinded path */
80+
if (od->fronting_nodes)
81+
return true;
82+
7983
node_id_from_pubkey(&local_nodeid, &od->id);
8084

8185
node = gossmap_find_node(gossmap, &local_nodeid);
@@ -1511,6 +1515,25 @@ static struct command_result *json_decode(struct command *cmd,
15111515
return command_finished(cmd, response);
15121516
}
15131517

1518+
static struct pubkey *json_to_pubkeys(const tal_t *ctx,
1519+
const char *buffer,
1520+
const jsmntok_t *tok)
1521+
{
1522+
size_t i;
1523+
const jsmntok_t *t;
1524+
struct pubkey *arr;
1525+
1526+
if (tok->type != JSMN_ARRAY)
1527+
return NULL;
1528+
1529+
arr = tal_arr(ctx, struct pubkey, tok->size);
1530+
json_for_each_arr(i, t, tok) {
1531+
if (!json_to_pubkey(buffer, t, &arr[i]))
1532+
return tal_free(arr);
1533+
}
1534+
return arr;
1535+
}
1536+
15141537
static const char *init(struct command *init_cmd,
15151538
const char *buf UNUSED,
15161539
const jsmntok_t *config UNUSED)
@@ -1528,8 +1551,13 @@ static const char *init(struct command *init_cmd,
15281551
rpc_scan(init_cmd, "listconfigs",
15291552
take(json_out_obj(NULL, NULL, NULL)),
15301553
"{configs:"
1531-
"{cltv-final:{value_int:%}}}",
1532-
JSON_SCAN(json_to_u16, &od->cltv_final));
1554+
"{cltv-final:{value_int:%},"
1555+
"payment-fronting-node?:{values_str:%}}}",
1556+
JSON_SCAN(json_to_u16, &od->cltv_final),
1557+
JSON_SCAN_TAL(od, json_to_pubkeys, &od->fronting_nodes));
1558+
/* Keep it simple if no fronting nodes */
1559+
if (tal_count(od->fronting_nodes) == 0)
1560+
od->fronting_nodes = tal_free(od->fronting_nodes);
15331561

15341562
rpc_scan(init_cmd, "makesecret",
15351563
take(json_out_obj(NULL, "string", BOLT12_ID_BASE_STRING)),

plugins/offers.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ struct offers_data {
2323
struct secret offerblinding_base;
2424
/* Base for node aliases for invoice requests */
2525
struct secret nodealias_base;
26+
/* Any --payment-fronting-node specified */
27+
struct pubkey *fronting_nodes;
2628
/* --dev-invoice-bpath-scid */
2729
bool dev_invoice_bpath_scid;
2830
/* --dev-invoice-internal-scid */

plugins/offers_invreq_hook.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,10 @@ static struct command_result *create_invoicereq(struct command *cmd,
253253
return send_outreq(req);
254254
}
255255

256+
/* FIXME: Allow multihop! */
257+
/* FIXME: And add padding! */
258+
259+
256260
/* FIXME: This is naive:
257261
* - Only creates if we have no public channels.
258262
* - Always creates a path from direct neighbor.

plugins/offers_offer.c

Lines changed: 70 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,50 @@ static struct command_result *create_offer(struct command *cmd,
252252
return send_outreq(req);
253253
}
254254

255+
/* Create num_node_ids paths from these node_ids to us (one hop each) */
256+
static struct blinded_path **offer_onehop_paths(const tal_t *ctx,
257+
const struct offers_data *od,
258+
const struct tlv_offer *offer,
259+
const struct pubkey *neighbors,
260+
size_t num_neigbors)
261+
{
262+
struct pubkey *ids = tal_arr(tmpctx, struct pubkey, 2);
263+
struct secret blinding_path_secret;
264+
struct sha256 offer_id;
265+
struct blinded_path **offer_paths;
266+
267+
/* Note: "id" of offer minus paths */
268+
assert(!offer->offer_paths);
269+
offer_offer_id(offer, &offer_id);
270+
271+
offer_paths = tal_arr(ctx, struct blinded_path *, num_neigbors);
272+
for (size_t i = 0; i < num_neigbors; i++) {
273+
ids[0] = neighbors[i];
274+
ids[1] = od->id;
275+
276+
/* So we recognize this */
277+
/* We can check this when they try to take up offer. */
278+
bolt12_path_secret(&od->offerblinding_base, &offer_id,
279+
&blinding_path_secret);
280+
281+
offer_paths[i]
282+
= incoming_message_blinded_path(offer_paths,
283+
ids,
284+
NULL,
285+
&blinding_path_secret);
286+
}
287+
return offer_paths;
288+
}
289+
290+
/* Common case of making a single path */
291+
static struct blinded_path **offer_onehop_path(const tal_t *ctx,
292+
const struct offers_data *od,
293+
const struct tlv_offer *offer,
294+
const struct pubkey *neighbor)
295+
{
296+
return offer_onehop_paths(ctx, od, offer, neighbor, 1);
297+
}
298+
255299
static struct command_result *found_best_peer(struct command *cmd,
256300
const struct chaninfo *best,
257301
struct offer_info *offinfo)
@@ -268,29 +312,9 @@ static struct command_result *found_best_peer(struct command *cmd,
268312
plugin_log(cmd->plugin, LOG_UNUSUAL,
269313
"No incoming channel to public peer, so no blinded path");
270314
} else {
271-
struct pubkey *ids;
272-
struct secret blinding_path_secret;
273-
struct sha256 offer_id;
274-
275-
/* Note: "id" of offer minus paths */
276-
offer_offer_id(offinfo->offer, &offer_id);
277-
278-
/* Make a small 1-hop path to us */
279-
ids = tal_arr(tmpctx, struct pubkey, 2);
280-
ids[0] = best->id;
281-
ids[1] = od->id;
282-
283-
/* So we recognize this */
284-
/* We can check this when they try to take up offer. */
285-
bolt12_path_secret(&od->offerblinding_base, &offer_id,
286-
&blinding_path_secret);
287-
288-
offinfo->offer->offer_paths = tal_arr(offinfo->offer, struct blinded_path *, 1);
289-
offinfo->offer->offer_paths[0]
290-
= incoming_message_blinded_path(offinfo->offer->offer_paths,
291-
ids,
292-
NULL,
293-
&blinding_path_secret);
315+
offinfo->offer->offer_paths
316+
= offer_onehop_path(offinfo->offer, od,
317+
offinfo->offer, &best->id);
294318
}
295319

296320
return create_offer(cmd, offinfo);
@@ -299,16 +323,31 @@ static struct command_result *found_best_peer(struct command *cmd,
299323
static struct command_result *maybe_add_path(struct command *cmd,
300324
struct offer_info *offinfo)
301325
{
302-
/* BOLT #12:
303-
* - if it is connected only by private channels:
304-
* - MUST include `offer_paths` containing one or more paths to the node from
305-
* publicly reachable nodes.
306-
*/
326+
const struct offers_data *od = get_offers_data(cmd->plugin);
327+
328+
/* Populate paths assuming not already set by dev_paths */
307329
if (!offinfo->offer->offer_paths) {
308-
if (we_want_blinded_path(cmd->plugin, false))
309-
return find_best_peer(cmd, OPT_ONION_MESSAGES,
310-
found_best_peer, offinfo);
330+
/* BOLT #12:
331+
* - if it is connected only by private channels:
332+
* - MUST include `offer_paths` containing one or more paths to the node from
333+
* publicly reachable nodes.
334+
*/
335+
if (we_want_blinded_path(cmd->plugin, false)) {
336+
/* We use *all* fronting nodes (not just "best" one)
337+
* for offers */
338+
if (od->fronting_nodes) {
339+
offinfo->offer->offer_paths
340+
= offer_onehop_paths(offinfo->offer, od,
341+
offinfo->offer,
342+
od->fronting_nodes,
343+
tal_count(od->fronting_nodes));
344+
} else {
345+
return find_best_peer(cmd, OPT_ONION_MESSAGES,
346+
found_best_peer, offinfo);
347+
}
348+
}
311349
}
350+
312351
return create_offer(cmd, offinfo);
313352
}
314353

tests/test_invoices.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -940,9 +940,12 @@ def test_invoice_botched_migration(node_factory, chainparams):
940940

941941

942942
def test_payment_fronting(node_factory):
943-
l1, l2 = node_factory.get_nodes(2)
944-
l3, l4 = node_factory.get_nodes(2, opts=[{'payment-fronting-node': l1.info['id']},
945-
{'payment-fronting-node': [l1.info['id'], l2.info['id']]}])
943+
# Nodes will front for offers if they don't have an advertized address, so allow localhost.
944+
l1, l2 = node_factory.get_nodes(2, opts={'dev-allow-localhost': None})
945+
l3, l4 = node_factory.get_nodes(2, opts=[{'payment-fronting-node': l1.info['id'],
946+
'dev-allow-localhost': None},
947+
{'payment-fronting-node': [l1.info['id'], l2.info['id']],
948+
'dev-allow-localhost': None}])
946949

947950
assert l3.rpc.listconfigs('payment-fronting-node') == {'configs': {'payment-fronting-node': {'sources': ['cmdline'], 'values_str': [l1.info['id']]}}}
948951
assert l4.rpc.listconfigs('payment-fronting-node') == {'configs': {'payment-fronting-node': {'sources': ['cmdline', 'cmdline'], 'values_str': [l1.info['id'], l2.info['id']]}}}
@@ -962,3 +965,13 @@ def test_payment_fronting(node_factory):
962965

963966
l1.rpc.xpay(l3inv)
964967
l1.rpc.xpay(l4inv)
968+
969+
# Now test offers.
970+
l3offer = l3.rpc.offer(1000, 'l3offer', 'l3offer')['bolt12']
971+
assert only_one(l3.rpc.decode(l3offer)['offer_paths'])['first_node_id'] == l1.info['id']
972+
973+
l4offer = l4.rpc.offer(1000, 'l4offer', 'l4offer')['bolt12']
974+
assert [r['first_node_id'] for r in l4.rpc.decode(l4offer)['offer_paths']] == [l1.info['id'], l2.info['id']]
975+
976+
l1.rpc.fetchinvoice(l3offer)['invoice']
977+
l1.rpc.fetchinvoice(l4offer)['invoice']

0 commit comments

Comments
 (0)