mirror of
https://github.com/ElementsProject/lightning.git
synced 2024-11-19 01:43:36 +01:00
offers: generalize "best peer" logic into core.
We use this for invoices published by unannounced nodes: want something very similar for offers, so generalize and expose it. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
parent
ca1b469c1a
commit
55982fe549
@ -271,6 +271,90 @@ static struct command_result *onion_message_recv(struct command *cmd,
|
||||
return command_hook_success(cmd);
|
||||
}
|
||||
|
||||
struct find_best_peer_data {
|
||||
struct command_result *(*cb)(struct command *,
|
||||
const struct chaninfo *,
|
||||
void *);
|
||||
int needed_feature;
|
||||
void *arg;
|
||||
};
|
||||
|
||||
static struct command_result *listincoming_done(struct command *cmd,
|
||||
const char *buf,
|
||||
const jsmntok_t *result,
|
||||
struct find_best_peer_data *data)
|
||||
{
|
||||
const jsmntok_t *arr, *t;
|
||||
size_t i;
|
||||
struct chaninfo *best = NULL;
|
||||
|
||||
arr = json_get_member(buf, result, "incoming");
|
||||
json_for_each_arr(i, t, arr) {
|
||||
struct chaninfo ci;
|
||||
const jsmntok_t *pftok;
|
||||
u8 *features;
|
||||
const char *err;
|
||||
struct amount_msat feebase;
|
||||
|
||||
err = json_scan(tmpctx, buf, t,
|
||||
"{id:%,"
|
||||
"incoming_capacity_msat:%,"
|
||||
"htlc_min_msat:%,"
|
||||
"htlc_max_msat:%,"
|
||||
"fee_base_msat:%,"
|
||||
"fee_proportional_millionths:%,"
|
||||
"cltv_expiry_delta:%}",
|
||||
JSON_SCAN(json_to_pubkey, &ci.id),
|
||||
JSON_SCAN(json_to_msat, &ci.capacity),
|
||||
JSON_SCAN(json_to_msat, &ci.htlc_min),
|
||||
JSON_SCAN(json_to_msat, &ci.htlc_max),
|
||||
JSON_SCAN(json_to_msat, &feebase),
|
||||
JSON_SCAN(json_to_u32, &ci.feeppm),
|
||||
JSON_SCAN(json_to_u32, &ci.cltv));
|
||||
if (err) {
|
||||
plugin_log(cmd->plugin, LOG_BROKEN,
|
||||
"Could not parse listincoming: %s",
|
||||
err);
|
||||
continue;
|
||||
}
|
||||
ci.feebase = feebase.millisatoshis; /* Raw: feebase */
|
||||
|
||||
/* Not presented if there's no channel_announcement for peer:
|
||||
* we could use listpeers, but if it's private we probably
|
||||
* don't want to blinded route through it! */
|
||||
pftok = json_get_member(buf, t, "peer_features");
|
||||
if (!pftok)
|
||||
continue;
|
||||
features = json_tok_bin_from_hex(tmpctx, buf, pftok);
|
||||
if (!feature_offered(features, data->needed_feature))
|
||||
continue;
|
||||
|
||||
if (!best || amount_msat_greater(ci.capacity, best->capacity))
|
||||
best = tal_dup(tmpctx, struct chaninfo, &ci);
|
||||
}
|
||||
|
||||
/* Free data if they don't */
|
||||
tal_steal(tmpctx, data);
|
||||
return data->cb(cmd, best, data->arg);
|
||||
}
|
||||
|
||||
struct command_result *find_best_peer_(struct command *cmd,
|
||||
int needed_feature,
|
||||
struct command_result *(*cb)(struct command *,
|
||||
const struct chaninfo *,
|
||||
void *),
|
||||
void *arg)
|
||||
{
|
||||
struct out_req *req;
|
||||
struct find_best_peer_data *data = tal(cmd, struct find_best_peer_data);
|
||||
data->cb = cb;
|
||||
data->arg = arg;
|
||||
data->needed_feature = needed_feature;
|
||||
req = jsonrpc_request_start(cmd->plugin, cmd, "listincoming",
|
||||
listincoming_done, forward_error, data);
|
||||
return send_outreq(cmd->plugin, req);
|
||||
}
|
||||
|
||||
static const struct plugin_hook hooks[] = {
|
||||
{
|
||||
"onion_message_recv",
|
||||
|
@ -65,4 +65,28 @@ inject_onionmessage_(struct command *cmd,
|
||||
|
||||
/* Get the (latest) gossmap */
|
||||
struct gossmap *get_gossmap(struct plugin *plugin);
|
||||
|
||||
/* Get the best (private) channel */
|
||||
struct chaninfo {
|
||||
struct pubkey id;
|
||||
struct amount_msat capacity, htlc_min, htlc_max;
|
||||
u32 feebase, feeppm, cltv;
|
||||
};
|
||||
|
||||
/* Calls listpeerchannels, then cb with best peer (if any!) which has needed_feature */
|
||||
struct command_result *find_best_peer_(struct command *cmd,
|
||||
int needed_feature,
|
||||
struct command_result *(*cb)(struct command *,
|
||||
const struct chaninfo *,
|
||||
void *),
|
||||
void *arg);
|
||||
|
||||
#define find_best_peer(cmd, needed_feature, cb, arg) \
|
||||
find_best_peer_((cmd), (needed_feature), \
|
||||
typesafe_cb_preargs(struct command_result *, void *, \
|
||||
(cb), (arg), \
|
||||
struct command *, \
|
||||
const struct chaninfo *), \
|
||||
(arg))
|
||||
|
||||
#endif /* LIGHTNING_PLUGINS_OFFERS_H */
|
||||
|
@ -234,93 +234,16 @@ static struct command_result *create_invoicereq(struct command *cmd,
|
||||
return send_outreq(cmd->plugin, req);
|
||||
}
|
||||
|
||||
/* If we only have private channels, we need to add a blinded path to the
|
||||
* invoice. We need to choose a peer who supports blinded payments, too. */
|
||||
struct chaninfo {
|
||||
struct pubkey id;
|
||||
struct short_channel_id scid;
|
||||
struct amount_msat capacity, htlc_min, htlc_max;
|
||||
u32 feebase, feeppm, cltv;
|
||||
bool public;
|
||||
};
|
||||
|
||||
/* FIXME: This is naive:
|
||||
* - Only creates if we have no public channels.
|
||||
* - Always creates a path from direct neighbor.
|
||||
* - Doesn't append dummy hops.
|
||||
* - Doesn't pad to length.
|
||||
*/
|
||||
/* (We only create if we have to, because our code doesn't handle
|
||||
* making a payment if the blinded path starts with ourselves!) */
|
||||
static struct command_result *listincoming_done(struct command *cmd,
|
||||
const char *buf,
|
||||
const jsmntok_t *result,
|
||||
struct invreq *ir)
|
||||
static struct command_result *found_best_peer(struct command *cmd,
|
||||
const struct chaninfo *best,
|
||||
struct invreq *ir)
|
||||
{
|
||||
const jsmntok_t *arr, *t;
|
||||
size_t i;
|
||||
struct chaninfo *best = NULL;
|
||||
bool any_public = false;
|
||||
|
||||
arr = json_get_member(buf, result, "incoming");
|
||||
json_for_each_arr(i, t, arr) {
|
||||
struct chaninfo ci;
|
||||
const jsmntok_t *pftok;
|
||||
u8 *features;
|
||||
const char *err;
|
||||
struct amount_msat feebase;
|
||||
|
||||
err = json_scan(tmpctx, buf, t,
|
||||
"{id:%,"
|
||||
"incoming_capacity_msat:%,"
|
||||
"htlc_min_msat:%,"
|
||||
"htlc_max_msat:%,"
|
||||
"fee_base_msat:%,"
|
||||
"fee_proportional_millionths:%,"
|
||||
"cltv_expiry_delta:%,"
|
||||
"short_channel_id:%,"
|
||||
"public:%}",
|
||||
JSON_SCAN(json_to_pubkey, &ci.id),
|
||||
JSON_SCAN(json_to_msat, &ci.capacity),
|
||||
JSON_SCAN(json_to_msat, &ci.htlc_min),
|
||||
JSON_SCAN(json_to_msat, &ci.htlc_max),
|
||||
JSON_SCAN(json_to_msat, &feebase),
|
||||
JSON_SCAN(json_to_u32, &ci.feeppm),
|
||||
JSON_SCAN(json_to_u32, &ci.cltv),
|
||||
JSON_SCAN(json_to_short_channel_id, &ci.scid),
|
||||
JSON_SCAN(json_to_bool, &ci.public));
|
||||
if (err) {
|
||||
plugin_log(cmd->plugin, LOG_BROKEN,
|
||||
"Could not parse listincoming: %s",
|
||||
err);
|
||||
continue;
|
||||
}
|
||||
ci.feebase = feebase.millisatoshis; /* Raw: feebase */
|
||||
any_public |= ci.public;
|
||||
|
||||
/* Not presented if there's no channel_announcement for peer:
|
||||
* we could use listpeers, but if it's private we probably
|
||||
* don't want to blinded route through it! */
|
||||
pftok = json_get_member(buf, t, "peer_features");
|
||||
if (!pftok)
|
||||
continue;
|
||||
features = json_tok_bin_from_hex(tmpctx, buf, pftok);
|
||||
if (!feature_offered(features, OPT_ROUTE_BLINDING))
|
||||
continue;
|
||||
|
||||
if (amount_msat_less(ci.htlc_max,
|
||||
amount_msat(*ir->inv->invoice_amount)))
|
||||
continue;
|
||||
|
||||
/* Only pick a private one if no public candidates. */
|
||||
if (!best || (!best->public && ci.public))
|
||||
best = tal_dup(tmpctx, struct chaninfo, &ci);
|
||||
}
|
||||
|
||||
/* If there are any public channels, don't add. */
|
||||
if (any_public)
|
||||
goto done;
|
||||
|
||||
/* BOLT-offers #12:
|
||||
* - MUST include `invoice_paths` containing one or more paths to the node.
|
||||
* - MUST specify `invoice_paths` in order of most-preferred to
|
||||
@ -392,18 +315,21 @@ static struct command_result *listincoming_done(struct command *cmd,
|
||||
ir->inv->invoice_blindedpay[0]->features = NULL;
|
||||
}
|
||||
|
||||
done:
|
||||
return create_invoicereq(cmd, ir);
|
||||
}
|
||||
|
||||
static struct command_result *add_blindedpaths(struct command *cmd,
|
||||
struct invreq *ir)
|
||||
{
|
||||
struct out_req *req;
|
||||
struct node_id local_nodeid;
|
||||
|
||||
req = jsonrpc_request_start(cmd->plugin, cmd, "listincoming",
|
||||
listincoming_done, listincoming_done, ir);
|
||||
return send_outreq(cmd->plugin, req);
|
||||
/* Don't bother if we're public */
|
||||
node_id_from_pubkey(&local_nodeid, &id);
|
||||
if (gossmap_find_node(get_gossmap(cmd->plugin), &local_nodeid))
|
||||
create_invoicereq(cmd, ir);
|
||||
|
||||
return find_best_peer(cmd, OPT_ROUTE_BLINDING,
|
||||
found_best_peer, ir);
|
||||
}
|
||||
|
||||
static struct command_result *check_period(struct command *cmd,
|
||||
|
Loading…
Reference in New Issue
Block a user