Skip to content

Commit 4b63d13

Browse files
committed
offers: only use blinded path nodes from offers when creating invoice for invoice_request.
Signed-off-by: Rusty Russell <[email protected]>
1 parent 7200b8a commit 4b63d13

File tree

2 files changed

+63
-6
lines changed

2 files changed

+63
-6
lines changed

plugins/offers_invreq_hook.c

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ struct invreq {
3939

4040
/* Optional secret. */
4141
const struct secret *secret;
42+
43+
/* Fronting nodes to use for invoice. */
44+
const struct pubkey *fronting_nodes;
4245
};
4346

4447
static struct command_result *WARN_UNUSED_RESULT
@@ -278,9 +281,11 @@ static struct command_result *found_best_peer(struct command *cmd,
278281
*/
279282
if (!best) {
280283
/* Don't allow bare invoices if they explicitly told us to front */
281-
if (od->fronting_nodes) {
284+
if (ir->fronting_nodes) {
282285
return fail_invreq(cmd, ir,
283-
"Could not find path from payment-fronting-node");
286+
"Could not find path from %zu nodes (%s)",
287+
tal_count(ir->fronting_nodes),
288+
fmt_pubkey(tmpctx, ir->fronting_nodes));
284289
}
285290

286291
/* Note: since we don't make one, createinvoice adds a dummy. */
@@ -393,12 +398,10 @@ static struct command_result *found_best_peer(struct command *cmd,
393398
static struct command_result *add_blindedpaths(struct command *cmd,
394399
struct invreq *ir)
395400
{
396-
const struct offers_data *od = get_offers_data(cmd->plugin);
397-
398-
if (!we_want_blinded_path(cmd->plugin, od->fronting_nodes, true))
401+
if (!we_want_blinded_path(cmd->plugin, ir->fronting_nodes, true))
399402
return create_invoicereq(cmd, ir);
400403

401-
return find_best_peer(cmd, OPT_ROUTE_BLINDING, od->fronting_nodes,
404+
return find_best_peer(cmd, OPT_ROUTE_BLINDING, ir->fronting_nodes,
402405
found_best_peer, ir);
403406
}
404407

@@ -828,6 +831,7 @@ static struct command_result *listoffers_done(struct command *cmd,
828831
struct command_result *err;
829832
struct amount_msat amt;
830833
struct tlv_invoice_request_invreq_recurrence_cancel *cancel;
834+
struct pubkey *offer_fronts;
831835

832836
/* BOLT #12:
833837
*
@@ -913,6 +917,29 @@ static struct command_result *listoffers_done(struct command *cmd,
913917
return fail_invreq(cmd, ir, "Offer expired");
914918
}
915919

920+
/* If offer used fronting nodes, we use them too. */
921+
offer_fronts = tal_arr(NULL, struct pubkey, 0);
922+
for (size_t i = 0; i < tal_count(ir->invreq->offer_paths); i++) {
923+
const struct blinded_path *p = ir->invreq->offer_paths[i];
924+
struct sciddir_or_pubkey first = p->first_node_id;
925+
926+
/* In dev mode we could set this. Fail if we can't map */
927+
if (!first.is_pubkey && !gossmap_scidd_pubkey(get_gossmap(cmd->plugin), &first))
928+
return fail_invreq(cmd, ir, "Fronting failed");
929+
assert(first.is_pubkey);
930+
/* Self-paths are not fronting nodes */
931+
if (!pubkey_eq(&od->id, &first.pubkey))
932+
tal_arr_expand(&offer_fronts, first.pubkey);
933+
}
934+
935+
if (tal_count(offer_fronts) != 0)
936+
ir->fronting_nodes = tal_steal(ir, offer_fronts);
937+
else {
938+
/* Otherwise, use defaults */
939+
tal_free(offer_fronts);
940+
ir->fronting_nodes = od->fronting_nodes;
941+
}
942+
916943
/* BOLT-recurrence #12:
917944
* - if `offer_quantity_max` is present:
918945
* - MUST reject the invoice request if `invreq_recurrence_cancel`

tests/test_invoices.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -991,3 +991,33 @@ def test_payment_fronting(node_factory):
991991
l3invreq = l3.rpc.invoicerequest(amount=1000, description='l3invreq')['bolt12']
992992
assert only_one(l3.rpc.decode(l3invreq)['invreq_paths'])['first_node_id'] == l1.info['id']
993993
l4.rpc.sendinvoice(invreq=l3invreq, label='l3invreq')
994+
995+
996+
def test_offer_fronting(node_factory):
997+
# l1 -> l2 -> l3
998+
# \ /
999+
# l4
1000+
# Nodes will front for offers if they don't have an advertized address.
1001+
l1, l2, l3, l4 = node_factory.get_nodes(4, opts={'dev-allow-localhost': None})
1002+
node_factory.join_nodes([l1, l2, l3], wait_for_announce=True)
1003+
node_factory.join_nodes([l2, l4], wait_for_announce=True)
1004+
node_factory.join_nodes([l3, l4], wait_for_announce=True)
1005+
1006+
offer_nofront = l4.rpc.offer("any", "nofront")['bolt12']
1007+
assert 'offer_paths' not in l1.rpc.decode(offer_nofront)
1008+
offer_front_l2 = l4.rpc.offer("any", "frontl2", fronting_nodes=[l2.info['id']])['bolt12']
1009+
assert only_one(l1.rpc.decode(offer_front_l2)['offer_paths'])['first_node_id'] == l2.info['id']
1010+
offer_front_l2l3 = l4.rpc.offer("any", "frontl2l3", fronting_nodes=[l2.info['id'], l3.info['id']])['bolt12']
1011+
assert [p['first_node_id'] for p in l1.rpc.decode(offer_front_l2)['offer_paths']] == [l2.info['id'], l3.info['id']]
1012+
1013+
inv_nofront = l1.rpc.fetchinvoice(offer_nofront, 1)['invoice']
1014+
assert only_one(l1.rpc.decode(inv_nofront)['invoice_paths'])['first_node_id'] == l4.info['id']
1015+
l1.rpc.xpay(inv_nofront)
1016+
1017+
inv_front_l2 = l1.rpc.fetchinvoice(offer_front_l2, 2)['invoice']
1018+
assert only_one(l1.rpc.decode(inv_front_l2)['invoice_paths'])['first_node_id'] == l2.info['id']
1019+
l1.rpc.xpay(inv_front_l2)
1020+
1021+
inv_front_l2l3 = l1.rpc.fetchinvoice(offer_front_l2l3, 3)['invoice']
1022+
assert only_one(l1.rpc.decode(inv_front_l2l3)['invoice_paths'])['first_node_id'] in (l2.info['id'], l3.info['id'])
1023+
l1.rpc.xpay(inv_front_l2l3)

0 commit comments

Comments
 (0)