offers: modify find_best_peer() to only select from fronting nodes if set.
What changed, and why it matters
This change adds an optional filter to a peer-selection helper in the Lightning node's 'offers' plugin. When a caller provides a list of allowed 'fronting' nodes, the helper now restricts its choice to those nodes and still keeps disconnected ones as a last resort. The commit message and diff do not describe any security bug; it reads like a feature refinement for offer/blinded-path routing.
Treat as a routine feature/refinement commit. No immediate security action is warranted based on the supplied materials. If reviewing for deployment, verify that fronting-only filtering is invoked correctly by future callers and does not inadvertently leak channel topology or weaken blinded-path privacy.
Security signals we found
Behavioral change in peer selection logic for BOLT 12 offers/blinded paths
New 'fronting_only' constraint could affect privacy properties of blinded path construction if misused
No explicit security relevance, incident reference, or vulnerability description present
Evidence from the diff
The patch modifies find_best_peer() in plugins/offers.c to accept a new ‘fronting_only’ pubkey array. If set, listincoming_done() skips any peer whose node ID is not in that array. For matching but disconnected peers, it degrades capacity to zero and marks them enabled so they are considered only after better candidates. The macro and all callers are updated; existing callers pass NULL, preserving prior behavior. No vulnerability, CVE, or security disclosure is mentioned in the commit or supplied references.
Changed components
plugins/offers.cplugins/offers.hplugins/offers_invreq_hook.cplugins/offers_offer.cCore Lightning offers plugin peer selectionInspect captured patch +31 / −4
diff --git a/plugins/offers.c b/plugins/offers.c
index 79b13065..547eeab5 100644
--- a/plugins/offers.c
+++ b/plugins/offers.c
@@ -318,9 +318,20 @@ struct find_best_peer_data {
const struct chaninfo *,
void *);
u64 needed_features;
+ const struct pubkey *fronting_only;
void *arg;
};
+static bool is_in_pubkeys(const struct pubkey *pubkeys,
+ const struct pubkey *k)
+{
+ for (size_t i = 0; i < tal_count(pubkeys); i++) {
+ if (pubkey_eq(&pubkeys[i], k))
+ return true;
+ }
+ return false;
+}
+
static struct command_result *listincoming_done(struct command *cmd,
const char *method,
const char *buf,
@@ -366,6 +377,18 @@ static struct command_result *listincoming_done(struct command *cmd,
}
ci.feebase = feebase.millisatoshis; /* Raw: feebase */
+ if (data->fronting_only) {
+ if (!is_in_pubkeys(data->fronting_only, &ci.id))
+ continue;
+
+ /* If disconnected, don't eliminate, simply
+ * consider it last. */
+ if (!enabled) {
+ ci.capacity = AMOUNT_MSAT(0);
+ enabled = true;
+ }
+ }
+
/* Don't pick a peer which is disconnected */
if (!enabled)
continue;
@@ -400,6 +423,7 @@ static struct command_result *listincoming_done(struct command *cmd,
struct command_result *find_best_peer_(struct command *cmd,
u64 needed_features,
+ const struct pubkey *fronting_only,
struct command_result *(*cb)(struct command *,
const struct chaninfo *,
void *),
@@ -410,6 +434,7 @@ struct command_result *find_best_peer_(struct command *cmd,
data->cb = cb;
data->arg = arg;
data->needed_features = needed_features;
+ data->fronting_only = fronting_only;
req = jsonrpc_request_start(cmd, "listincoming",
listincoming_done, forward_error, data);
return send_outreq(req);
diff --git a/plugins/offers.h b/plugins/offers.h
index 13968a0e..95a6ea11 100644
--- a/plugins/offers.h
+++ b/plugins/offers.h
@@ -86,13 +86,14 @@ struct chaninfo {
/* Calls listpeerchannels, then cb with best peer (if any!) which has needed_feature */
struct command_result *find_best_peer_(struct command *cmd,
u64 needed_features,
+ const struct pubkey *fronting_only,
struct command_result *(*cb)(struct command *,
const struct chaninfo *,
void *),
void *arg);
-#define find_best_peer(cmd, needed_features, cb, arg) \
- find_best_peer_((cmd), (needed_features), \
+#define find_best_peer(cmd, needed_features, fronting_only, cb, arg) \
+ find_best_peer_((cmd), (needed_features), (fronting_only), \
typesafe_cb_preargs(struct command_result *, void *, \
(cb), (arg), \
struct command *, \
diff --git a/plugins/offers_invreq_hook.c b/plugins/offers_invreq_hook.c
index 38de98a6..0a330597 100644
--- a/plugins/offers_invreq_hook.c
+++ b/plugins/offers_invreq_hook.c
@@ -393,7 +393,7 @@ static struct command_result *add_blindedpaths(struct command *cmd,
* us onion messaging. */
return find_best_peer(cmd,
(1ULL << OPT_ROUTE_BLINDING) | (1ULL << OPT_ONION_MESSAGES),
- found_best_peer, ir);
+ NULL, found_best_peer, ir);
}
static struct command_result *cancel_invoice(struct command *cmd,
diff --git a/plugins/offers_offer.c b/plugins/offers_offer.c
index 6729c1f8..797da61a 100644
--- a/plugins/offers_offer.c
+++ b/plugins/offers_offer.c
@@ -342,6 +342,7 @@ static struct command_result *maybe_add_path(struct command *cmd,
tal_count(od->fronting_nodes));
} else {
return find_best_peer(cmd, 1ULL << OPT_ONION_MESSAGES,
+ NULL,
found_best_peer, offinfo);
}
}
@@ -776,7 +777,7 @@ struct command_result *json_invoicerequest(struct command *cmd,
idata->single_use = *single_use;
idata->label = label;
return find_best_peer(cmd, 1ULL << OPT_ONION_MESSAGES,
- found_best_peer_invrequest, idata);
+ NULL, found_best_peer_invrequest, idata);
}
return call_createinvoicerequest(cmd, invreq, *single_use, label);
Why this scored 25/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.