Skip to content

Commit 4bd433a

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 6f3ca3b commit 4bd433a

5 files changed

Lines changed: 122 additions & 36 deletions

File tree

plugins/offers.c

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

81+
/* If we're fronting, we always want a blinded path */
82+
if (od->fronting_nodes)
83+
return true;
84+
8185
node_id_from_pubkey(&local_nodeid, &od->id);
8286

8387
node = gossmap_find_node(gossmap, &local_nodeid);
@@ -1614,6 +1618,25 @@ static struct command_result *json_decode(struct command *cmd,
16141618
return command_finished(cmd, response);
16151619
}
16161620

1621+
static struct pubkey *json_to_pubkeys(const tal_t *ctx,
1622+
const char *buffer,
1623+
const jsmntok_t *tok)
1624+
{
1625+
size_t i;
1626+
const jsmntok_t *t;
1627+
struct pubkey *arr;
1628+
1629+
if (tok->type != JSMN_ARRAY)
1630+
return NULL;
1631+
1632+
arr = tal_arr(ctx, struct pubkey, tok->size);
1633+
json_for_each_arr(i, t, tok) {
1634+
if (!json_to_pubkey(buffer, t, &arr[i]))
1635+
return tal_free(arr);
1636+
}
1637+
return arr;
1638+
}
1639+
16171640
static const char *init(struct command *init_cmd,
16181641
const char *buf UNUSED,
16191642
const jsmntok_t *config UNUSED)
@@ -1631,8 +1654,13 @@ static const char *init(struct command *init_cmd,
16311654
rpc_scan(init_cmd, "listconfigs",
16321655
take(json_out_obj(NULL, NULL, NULL)),
16331656
"{configs:"
1634-
"{cltv-final:{value_int:%}}}",
1635-
JSON_SCAN(json_to_u16, &od->cltv_final));
1657+
"{cltv-final:{value_int:%},"
1658+
"payment-fronting-node?:{values_str:%}}}",
1659+
JSON_SCAN(json_to_u16, &od->cltv_final),
1660+
JSON_SCAN_TAL(od, json_to_pubkeys, &od->fronting_nodes));
1661+
/* Keep it simple if no fronting nodes */
1662+
if (tal_count(od->fronting_nodes) == 0)
1663+
od->fronting_nodes = tal_free(od->fronting_nodes);
16361664

16371665
rpc_scan(init_cmd, "makesecret",
16381666
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
@@ -250,6 +250,10 @@ static struct command_result *create_invoicereq(struct command *cmd,
250250
return send_outreq(req);
251251
}
252252

253+
/* FIXME: Allow multihop! */
254+
/* FIXME: And add padding! */
255+
256+
253257
/* FIXME: This is naive:
254258
* - Only creates if we have no public channels.
255259
* - 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
@@ -251,6 +251,50 @@ static struct command_result *create_offer(struct command *cmd,
251251
return send_outreq(req);
252252
}
253253

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

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

tests/test_invoices.py

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

944944

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

950953
assert l3.rpc.listconfigs('payment-fronting-node') == {'configs': {'payment-fronting-node': {'sources': ['cmdline'], 'values_str': [l1.info['id']]}}}
951954
assert l4.rpc.listconfigs('payment-fronting-node') == {'configs': {'payment-fronting-node': {'sources': ['cmdline', 'cmdline'], 'values_str': [l1.info['id'], l2.info['id']]}}}
@@ -966,6 +969,16 @@ def test_payment_fronting(node_factory):
966969
l1.rpc.xpay(l3inv)
967970
l1.rpc.xpay(l4inv)
968971

972+
# Now test offers.
973+
l3offer = l3.rpc.offer(1000, 'l3offer', 'l3offer')['bolt12']
974+
assert only_one(l3.rpc.decode(l3offer)['offer_paths'])['first_node_id'] == l1.info['id']
975+
976+
l4offer = l4.rpc.offer(1000, 'l4offer', 'l4offer')['bolt12']
977+
assert [r['first_node_id'] for r in l4.rpc.decode(l4offer)['offer_paths']] == [l1.info['id'], l2.info['id']]
978+
979+
l1.rpc.fetchinvoice(l3offer)['invoice']
980+
l1.rpc.fetchinvoice(l4offer)['invoice']
981+
969982

970983
def test_invoice_maxdesc(node_factory, chainparams):
971984
l1, l2 = node_factory.line_graph(2)

0 commit comments

Comments
 (0)