Skip to content

Commit 9fa7c93

Browse files
committed
offers: only use blinded path nodes from offers when creating invoice for invoice_request.
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
1 parent 64e4519 commit 9fa7c93

2 files changed

Lines changed: 80 additions & 6 deletions

File tree

plugins/offers_invreq_hook.c

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ struct invreq {
3636

3737
/* Optional secret. */
3838
const struct secret *secret;
39+
40+
/* Fronting nodes to use for invoice. */
41+
const struct pubkey *fronting_nodes;
3942
};
4043

4144
static struct command_result *WARN_UNUSED_RESULT
@@ -275,9 +278,12 @@ static struct command_result *found_best_peer(struct command *cmd,
275278
*/
276279
if (!best) {
277280
/* Don't allow bare invoices if they explicitly told us to front */
278-
if (od->fronting_nodes) {
281+
if (ir->fronting_nodes) {
279282
return fail_invreq(cmd, ir,
280-
"Could not find path from payment-fronting-node");
283+
"Could not find path from %zu nodes (%s%s)",
284+
tal_count(ir->fronting_nodes),
285+
fmt_pubkey(tmpctx, &ir->fronting_nodes[0]),
286+
tal_count(ir->fronting_nodes) > 1 ? ", ..." : "");
281287
}
282288

283289
/* Note: since we don't make one, createinvoice adds a dummy. */
@@ -390,9 +396,7 @@ static struct command_result *found_best_peer(struct command *cmd,
390396
static struct command_result *add_blindedpaths(struct command *cmd,
391397
struct invreq *ir)
392398
{
393-
const struct offers_data *od = get_offers_data(cmd->plugin);
394-
395-
if (!we_want_blinded_path(cmd->plugin, od->fronting_nodes, true))
399+
if (!we_want_blinded_path(cmd->plugin, ir->fronting_nodes, true))
396400
return create_invoicereq(cmd, ir);
397401

398402
/* Technically, this only needs OPT_ROUTE_BLINDING, but we have a report
@@ -401,7 +405,7 @@ static struct command_result *add_blindedpaths(struct command *cmd,
401405
* us onion messaging. */
402406
return find_best_peer(cmd,
403407
(1ULL << OPT_ROUTE_BLINDING) | (1ULL << OPT_ONION_MESSAGES),
404-
od->fronting_nodes, found_best_peer, ir);
408+
ir->fronting_nodes, found_best_peer, ir);
405409
}
406410

407411
static struct command_result *cancel_invoice(struct command *cmd,
@@ -830,6 +834,7 @@ static struct command_result *listoffers_done(struct command *cmd,
830834
struct command_result *err;
831835
struct amount_msat amt;
832836
struct tlv_invoice_request_invreq_recurrence_cancel *cancel;
837+
struct pubkey *offer_fronts;
833838

834839
/* BOLT #12:
835840
*
@@ -915,6 +920,37 @@ static struct command_result *listoffers_done(struct command *cmd,
915920
return fail_invreq(cmd, ir, "Offer expired");
916921
}
917922

923+
/* If offer used fronting nodes, we use them too. */
924+
offer_fronts = tal_arr(ir, struct pubkey, 0);
925+
for (size_t i = 0; i < tal_count(ir->invreq->offer_paths); i++) {
926+
const struct blinded_path *p = ir->invreq->offer_paths[i];
927+
struct sciddir_or_pubkey first = p->first_node_id;
928+
929+
/* In dev mode we could set this. Ignore if we can't map */
930+
if (!first.is_pubkey && !gossmap_scidd_pubkey(get_gossmap(cmd->plugin), &first)) {
931+
plugin_log(cmd->plugin, LOG_UNUSUAL,
932+
"Can't find front %s, ignoring in %s",
933+
fmt_sciddir_or_pubkey(tmpctx, &p->first_node_id),
934+
invrequest_encode(tmpctx, ir->invreq));
935+
continue;
936+
}
937+
assert(first.is_pubkey);
938+
/* Self-paths are not fronting nodes */
939+
if (!pubkey_eq(&od->id, &first.pubkey))
940+
tal_arr_expand(&offer_fronts, first.pubkey);
941+
}
942+
if (tal_count(offer_fronts) != 0)
943+
ir->fronting_nodes = offer_fronts;
944+
else {
945+
/* Get upset if none from offer (via invreq) were usable! */
946+
if (tal_count(ir->invreq->offer_paths) != 0)
947+
return fail_invreq(cmd, ir, "Fronting failed, could not find any fronts");
948+
949+
/* Otherwise, use defaults */
950+
tal_free(offer_fronts);
951+
ir->fronting_nodes = od->fronting_nodes;
952+
}
953+
918954
/* BOLT-recurrence #12:
919955
* - if `offer_quantity_max` is present:
920956
* - MUST reject the invoice request if `invreq_recurrence_cancel`

tests/test_invoices.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -995,6 +995,44 @@ def test_payment_fronting(node_factory):
995995
assert only_one(l3.rpc.decode(l3invreq)['invreq_paths'])['first_node_id'] == l1.info['id']
996996
l4.rpc.sendinvoice(invreq=l3invreq, label='l3invreq')
997997

998+
# We can explicitly override offers: make it use a specific node
999+
l4offer_front2 = l4.rpc.offer(1000, 'l4offer', 'l4offer', fronting_nodes=[l2.info['id']])['bolt12']
1000+
assert [r['first_node_id'] for r in l4.rpc.decode(l4offer_front2)['offer_paths']] == [l2.info['id']]
1001+
1002+
# ... or make it not front at all
1003+
l4offer_nofront = l4.rpc.offer(1000, 'l4offer', 'l4offer', fronting_nodes=[])['bolt12']
1004+
assert 'offer_paths' not in l4.rpc.decode(l4offer_nofront)
1005+
1006+
1007+
def test_offer_fronting(node_factory):
1008+
# l1 -> l2 -> l3
1009+
# \ /
1010+
# l4
1011+
# Nodes will not front for offers if they don't have an advertized address.
1012+
l1, l2, l3, l4 = node_factory.get_nodes(4, opts={'dev-allow-localhost': None})
1013+
node_factory.join_nodes([l1, l2, l3], wait_for_announce=True)
1014+
node_factory.join_nodes([l2, l4], wait_for_announce=True)
1015+
node_factory.join_nodes([l3, l4], wait_for_announce=True)
1016+
1017+
offer_nofront = l4.rpc.offer("any", "nofront")['bolt12']
1018+
assert 'offer_paths' not in l1.rpc.decode(offer_nofront)
1019+
offer_front_l2 = l4.rpc.offer("any", "frontl2", fronting_nodes=[l2.info['id']])['bolt12']
1020+
assert only_one(l1.rpc.decode(offer_front_l2)['offer_paths'])['first_node_id'] == l2.info['id']
1021+
offer_front_l2l3 = l4.rpc.offer("any", "frontl2l3", fronting_nodes=[l2.info['id'], l3.info['id']])['bolt12']
1022+
assert [p['first_node_id'] for p in l1.rpc.decode(offer_front_l2l3)['offer_paths']] == [l2.info['id'], l3.info['id']]
1023+
1024+
inv_nofront = l1.rpc.fetchinvoice(offer_nofront, 1)['invoice']
1025+
assert only_one(l1.rpc.decode(inv_nofront)['invoice_paths'])['first_node_id'] == l4.info['id']
1026+
l1.rpc.xpay(inv_nofront)
1027+
1028+
inv_front_l2 = l1.rpc.fetchinvoice(offer_front_l2, 2)['invoice']
1029+
assert only_one(l1.rpc.decode(inv_front_l2)['invoice_paths'])['first_node_id'] == l2.info['id']
1030+
l1.rpc.xpay(inv_front_l2)
1031+
1032+
inv_front_l2l3 = l1.rpc.fetchinvoice(offer_front_l2l3, 3)['invoice']
1033+
assert only_one(l1.rpc.decode(inv_front_l2l3)['invoice_paths'])['first_node_id'] in (l2.info['id'], l3.info['id'])
1034+
l1.rpc.xpay(inv_front_l2l3)
1035+
9981036

9991037
def test_invoice_maxdesc(node_factory, chainparams):
10001038
l1, l2 = node_factory.line_graph(2)

0 commit comments

Comments
 (0)