plugins: move fetchinvoice functionality into offers plugin.

This means only a single gossmap, and they already share the fetchinvoice-noconnect option
and autoconnect code.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Changelog-Changed: Plugins: the `fetchinvoice` plugin has been combined into the `offers` plugin.
This commit is contained in:
Rusty Russell 2024-07-17 12:53:00 +09:30 committed by Vincenzo Palazzo
parent 1e1d072b65
commit a782ea75b5
10 changed files with 130 additions and 173 deletions

View file

@ -31,14 +31,10 @@ PLUGIN_PAY_LIB_SRC := plugins/libplugin-pay.c
PLUGIN_PAY_LIB_HEADER := plugins/libplugin-pay.h
PLUGIN_PAY_LIB_OBJS := $(PLUGIN_PAY_LIB_SRC:.c=.o)
PLUGIN_OFFERS_SRC := plugins/offers.c plugins/offers_offer.c plugins/offers_invreq_hook.c plugins/offers_inv_hook.c plugins/establish_onion_path.c
PLUGIN_OFFERS_SRC := plugins/offers.c plugins/offers_offer.c plugins/offers_invreq_hook.c plugins/offers_inv_hook.c plugins/establish_onion_path.c plugins/fetchinvoice.c
PLUGIN_OFFERS_OBJS := $(PLUGIN_OFFERS_SRC:.c=.o)
PLUGIN_OFFERS_HEADER := $(PLUGIN_OFFERS_SRC:.c=.h)
PLUGIN_FETCHINVOICE_SRC := plugins/fetchinvoice.c plugins/establish_onion_path.c
PLUGIN_FETCHINVOICE_OBJS := $(PLUGIN_FETCHINVOICE_SRC:.c=.o)
PLUGIN_FETCHINVOICE_HEADER :=
PLUGIN_SQL_SRC := plugins/sql.c
PLUGIN_SQL_HEADER :=
PLUGIN_SQL_OBJS := $(PLUGIN_SQL_SRC:.c=.o)
@ -72,7 +68,6 @@ PLUGIN_ALL_SRC := \
$(PLUGIN_chanbackup_SRC) \
$(PLUGIN_BCLI_SRC) \
$(PLUGIN_COMMANDO_SRC) \
$(PLUGIN_FETCHINVOICE_SRC) \
$(PLUGIN_FUNDER_SRC) \
$(PLUGIN_TOPOLOGY_SRC) \
$(PLUGIN_KEYSEND_SRC) \
@ -97,7 +92,6 @@ C_PLUGINS := \
plugins/chanbackup \
plugins/bcli \
plugins/commando \
plugins/fetchinvoice \
plugins/funder \
plugins/topology \
plugins/keysend \
@ -216,8 +210,6 @@ plugins/spenderp: bitcoin/block.o bitcoin/preimage.o bitcoin/psbt.o common/psbt_
plugins/offers: $(PLUGIN_OFFERS_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) common/addr.o common/bolt12.o common/bolt12_merkle.o common/bolt11_json.o common/iso4217.o $(WIRE_OBJS) $(WIRE_BOLT12_OBJS) bitcoin/block.o common/channel_id.o bitcoin/preimage.o common/blindedpath.o common/invoice_path_id.o common/blinding.o common/hmac.o common/json_blinded_path.o common/gossmap.o common/fp16.o $(JSMN_OBJS) common/dijkstra.o common/route.o common/gossmods_listpeerchannels.o
plugins/fetchinvoice: $(PLUGIN_FETCHINVOICE_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) common/bolt12.o common/bolt12_merkle.o common/iso4217.o $(WIRE_OBJS) $(WIRE_BOLT12_OBJS) bitcoin/block.o common/channel_id.o bitcoin/preimage.o $(JSMN_OBJS) common/gossmap.o common/fp16.o common/dijkstra.o common/route.o common/blindedpath.o common/hmac.o common/blinding.o common/gossmods_listpeerchannels.o
plugins/funder: bitcoin/psbt.o common/psbt_open.o $(PLUGIN_FUNDER_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS)
plugins/recover: common/gossmap.o common/fp16.o $(PLUGIN_RECOVER_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS)

View file

@ -18,13 +18,12 @@
#include <common/route.h>
#include <errno.h>
#include <plugins/establish_onion_path.h>
#include <plugins/fetchinvoice.h>
#include <plugins/libplugin.h>
#include <plugins/offers.h>
#include <secp256k1_schnorrsig.h>
#include <sodium.h>
static struct gossmap *global_gossmap;
static struct pubkey local_id;
static bool disable_connect = false;
static LIST_HEAD(sent_list);
struct sent {
@ -334,9 +333,9 @@ badinv:
return command_hook_success(cmd);
}
static struct command_result *recv_modern_onion_message(struct command *cmd,
const char *buf,
const jsmntok_t *params)
struct command_result *recv_modern_onion_message(struct command *cmd,
const char *buf,
const jsmntok_t *params)
{
const jsmntok_t *om, *secrettok;
struct sent *sent;
@ -346,6 +345,9 @@ static struct command_result *recv_modern_onion_message(struct command *cmd,
om = json_get_member(buf, params, "onion_message");
secrettok = json_get_member(buf, om, "pathsecret");
if (!secrettok)
return NULL;
json_to_secret(buf, secrettok, &pathsecret);
sent = find_sent_by_secret(&pathsecret);
if (!sent) {
@ -353,7 +355,7 @@ static struct command_result *recv_modern_onion_message(struct command *cmd,
"No match for modern onion %.*s",
json_tok_full_len(om),
json_tok_full(buf, om));
return command_hook_success(cmd);
return NULL;
}
plugin_log(cmd->plugin, LOG_DBG, "Received modern onion message: %.*s",
@ -367,7 +369,7 @@ static struct command_result *recv_modern_onion_message(struct command *cmd,
if (sent->invreq)
return handle_invreq_response(cmd, sent, buf, om);
return command_hook_success(cmd);
return NULL;
}
static void destroy_sent(struct sent *sent)
@ -397,31 +399,6 @@ static struct command_result *sendonionmsg_done(struct command *cmd,
return command_still_pending(cmd);
}
static void init_gossmap(struct plugin *plugin)
{
size_t num_cupdates_rejected;
global_gossmap
= notleak_with_children(gossmap_load(NULL,
GOSSIP_STORE_FILENAME,
&num_cupdates_rejected));
if (!global_gossmap)
plugin_err(plugin, "Could not load gossmap %s: %s",
GOSSIP_STORE_FILENAME, strerror(errno));
if (num_cupdates_rejected)
plugin_log(plugin, LOG_DBG,
"gossmap ignored %zu channel updates",
num_cupdates_rejected);
}
static struct gossmap *get_gossmap(struct plugin *plugin)
{
if (!global_gossmap)
init_gossmap(plugin);
else
gossmap_refresh(global_gossmap, NULL);
return global_gossmap;
}
static struct command_result *param_offer(struct command *cmd,
const char *name,
const char *buffer,
@ -548,7 +525,7 @@ static struct blinded_path *blinded_path(const tal_t *ctx,
sciddir_or_pubkey_from_scidd(&path->first_node_id, first_scidd);
else
sciddir_or_pubkey_from_pubkey(&path->first_node_id, &ids[0]);
assert(pubkey_eq(&ids[nhops-1], &local_id));
assert(pubkey_eq(&ids[nhops-1], &id));
randombytes_buf(&first_blinding, sizeof(first_blinding));
if (!pubkey_from_privkey(&first_blinding, &path->blinding))
@ -785,7 +762,7 @@ static struct command_result *invreq_done(struct command *cmd,
}
}
return establish_onion_path(cmd, get_gossmap(cmd->plugin), &local_id,
return establish_onion_path(cmd, get_gossmap(cmd->plugin), &id,
sent->invreq->offer_node_id,
disable_connect,
fetchinvoice_path_done,
@ -833,9 +810,9 @@ static struct command_result *param_dev_reply_path(struct command *cmd, const ch
}
/* Fetches an invoice for this offer, and makes sure it corresponds. */
static struct command_result *json_fetchinvoice(struct command *cmd,
const char *buffer,
const jsmntok_t *params)
struct command_result *json_fetchinvoice(struct command *cmd,
const char *buffer,
const jsmntok_t *params)
{
struct amount_msat *msat;
const char *rec_label, *payer_note;
@ -860,6 +837,10 @@ static struct command_result *json_fetchinvoice(struct command *cmd,
NULL))
return command_param_failed();
if (!offers_enabled)
return command_fail(cmd, LIGHTNINGD,
"experimental-offers not enabled");
sent->wait_timeout = *timeout;
/* BOLT-offers #12:
@ -1032,9 +1013,9 @@ static struct command_result *json_fetchinvoice(struct command *cmd,
* it's actually hit the db! But using waitinvoice is also suboptimal
* because we don't have libplugin infra to cancel a pending req (and I
* want to rewrite our wait* API anyway) */
static struct command_result *invoice_payment(struct command *cmd,
const char *buf,
const jsmntok_t *params)
struct command_result *invoice_payment(struct command *cmd,
const char *buf,
const jsmntok_t *params)
{
struct sent *i;
const jsmntok_t *ptok, *preimagetok, *msattok;
@ -1134,7 +1115,7 @@ static struct command_result *createinvoice_done(struct command *cmd,
"FIXME: support blinded paths!");
}
return establish_onion_path(cmd, get_gossmap(cmd->plugin), &local_id,
return establish_onion_path(cmd, get_gossmap(cmd->plugin), &id,
sent->invreq->invreq_payer_id,
disable_connect,
sendinvoice_path_done,
@ -1284,9 +1265,9 @@ static struct command_result *param_invreq(struct command *cmd,
return NULL;
}
static struct command_result *json_sendinvoice(struct command *cmd,
const char *buffer,
const jsmntok_t *params)
struct command_result *json_sendinvoice(struct command *cmd,
const char *buffer,
const jsmntok_t *params)
{
struct amount_msat *msat;
u32 *timeout;
@ -1304,6 +1285,10 @@ static struct command_result *json_sendinvoice(struct command *cmd,
NULL))
return command_param_failed();
if (!offers_enabled)
return command_fail(cmd, LIGHTNINGD,
"experimental-offers not enabled");
sent->dev_path_use_scidd = NULL;
sent->dev_reply_path = NULL;
@ -1359,7 +1344,7 @@ static struct command_result *json_sendinvoice(struct command *cmd,
*/
/* FIXME: Use transitory id! */
sent->inv->invoice_node_id = tal(sent->inv, struct pubkey);
sent->inv->invoice_node_id->pubkey = local_id.pubkey;
sent->inv->invoice_node_id->pubkey = id.pubkey;
/* BOLT-offers #12:
* - if the expiry for accepting payment is not 7200 seconds
@ -1404,9 +1389,9 @@ static struct command_result *param_raw_invreq(struct command *cmd,
return NULL;
}
static struct command_result *json_dev_rawrequest(struct command *cmd,
const char *buffer,
const jsmntok_t *params)
struct command_result *json_dev_rawrequest(struct command *cmd,
const char *buffer,
const jsmntok_t *params)
{
struct sent *sent = tal(cmd, struct sent);
u32 *timeout;
@ -1426,80 +1411,10 @@ static struct command_result *json_dev_rawrequest(struct command *cmd,
sent->dev_path_use_scidd = NULL;
sent->dev_reply_path = NULL;
return establish_onion_path(cmd, get_gossmap(cmd->plugin), &local_id,
return establish_onion_path(cmd, get_gossmap(cmd->plugin), &id,
node_id,
disable_connect,
fetchinvoice_path_done,
fetchinvoice_path_fail,
sent);
}
static const struct plugin_command commands[] = {
{
"fetchinvoice",
"payment",
"Request remote node for an invoice for this {offer}, with {amount}, {quanitity}, {recurrence_counter}, {recurrence_start} and {recurrence_label} iff required.",
NULL,
json_fetchinvoice,
},
{
"sendinvoice",
"payment",
"Request remote node for to pay this {invreq}, with {label}, optional {amount_msat}, and {timeout} (default 90 seconds).",
NULL,
json_sendinvoice,
},
{
"dev-rawrequest",
"util",
"Send {invreq} to {nodeid}, wait {timeout} (60 seconds by default)",
NULL,
json_dev_rawrequest,
.dev_only = true,
},
};
static const char *init(struct plugin *p, const char *buf UNUSED,
const jsmntok_t *config UNUSED)
{
bool exp_offers;
rpc_scan(p, "getinfo",
take(json_out_obj(NULL, NULL, NULL)),
"{id:%}", JSON_SCAN(json_to_pubkey, &local_id));
rpc_scan(p, "listconfigs",
take(json_out_obj(NULL, "config", "experimental-offers")),
"{configs:{experimental-offers:{set:%}}}",
JSON_SCAN(json_to_bool, &exp_offers));
if (!exp_offers)
return "offers not enabled in config";
return NULL;
}
static const struct plugin_hook hooks[] = {
{
"onion_message_recv_secret",
recv_modern_onion_message
},
{
"invoice_payment",
invoice_payment,
},
};
int main(int argc, char *argv[])
{
setup_locale();
plugin_main(argv, init, PLUGIN_RESTARTABLE, true, NULL,
commands, ARRAY_SIZE(commands),
/* No notifications */
NULL, 0,
hooks, ARRAY_SIZE(hooks),
NULL, 0,
plugin_option("fetchinvoice-noconnect", "flag",
"Don't try to connect directly to fetch an invoice.",
flag_option, flag_jsonfmt, &disable_connect),
NULL);
}

27
plugins/fetchinvoice.h Normal file
View file

@ -0,0 +1,27 @@
#ifndef LIGHTNING_PLUGINS_FETCHINVOICE_H
#define LIGHTNING_PLUGINS_FETCHINVOICE_H
#include "config.h"
struct command_result;
struct command;
struct command_result *json_fetchinvoice(struct command *cmd,
const char *buffer,
const jsmntok_t *params);
struct command_result *json_sendinvoice(struct command *cmd,
const char *buffer,
const jsmntok_t *params);
struct command_result *json_dev_rawrequest(struct command *cmd,
const char *buffer,
const jsmntok_t *params);
struct command_result *recv_modern_onion_message(struct command *cmd,
const char *buf,
const jsmntok_t *params);
struct command_result *invoice_payment(struct command *cmd,
const char *buf,
const jsmntok_t *params);
#endif /* LIGHTNING_PLUGINS_FETCHINVOICE_H */

View file

@ -18,6 +18,7 @@
#include <common/json_stream.h>
#include <common/memleak.h>
#include <errno.h>
#include <plugins/fetchinvoice.h>
#include <plugins/offers.h>
#include <plugins/offers_inv_hook.h>
#include <plugins/offers_invreq_hook.h>
@ -51,7 +52,7 @@ static void init_gossmap(struct plugin *plugin)
num_cupdates_rejected);
}
static struct gossmap *get_gossmap(struct plugin *plugin)
struct gossmap *get_gossmap(struct plugin *plugin)
{
if (!global_gossmap)
init_gossmap(plugin);
@ -162,10 +163,16 @@ static struct command_result *onion_message_modern_call(struct command *cmd,
{
const jsmntok_t *om, *replytok, *invreqtok, *invtok;
struct blinded_path *reply_path = NULL;
struct command_result *res;
if (!offers_enabled)
return command_hook_success(cmd);
/* FIXME: unify parsing! */
res = recv_modern_onion_message(cmd, buf, params);
if (res)
return res;
om = json_get_member(buf, params, "onion_message");
replytok = json_get_member(buf, om, "reply_blindedpath");
if (replytok) {
@ -203,6 +210,14 @@ static const struct plugin_hook hooks[] = {
"onion_message_recv",
onion_message_modern_call
},
{
"onion_message_recv_secret",
onion_message_modern_call
},
{
"invoice_payment",
invoice_payment,
},
};
static struct command_result *block_added_notify(struct command *cmd,
@ -1235,11 +1250,9 @@ static const char *init(struct plugin *p,
take(json_out_obj(NULL, NULL, NULL)),
"{configs:"
"{cltv-final:{value_int:%},"
"experimental-offers:{set:%}},"
"fetchinvoice-noconnect?:{set:%}}",
"experimental-offers:{set:%}}}",
JSON_SCAN(json_to_u16, &cltv_final),
JSON_SCAN(json_to_bool, &offers_enabled),
JSON_SCAN(json_to_bool, &disable_connect));
JSON_SCAN(json_to_bool, &offers_enabled));
rpc_scan(p, "makesecret",
take(json_out_obj(NULL, "string", INVOICE_PATH_BASE_STRING)),
@ -1271,8 +1284,31 @@ static const struct plugin_command commands[] = {
NULL,
json_decode,
},
{
"fetchinvoice",
"payment",
"Request remote node for an invoice for this {offer}, with {amount}, {quanitity}, {recurrence_counter}, {recurrence_start} and {recurrence_label} iff required.",
NULL,
json_fetchinvoice,
},
{
"sendinvoice",
"payment",
"Request remote node for to pay this {invreq}, with {label}, optional {amount_msat}, and {timeout} (default 90 seconds).",
NULL,
json_sendinvoice,
},
{
"dev-rawrequest",
"util",
"Send {invreq} to {nodeid}, wait {timeout} (60 seconds by default)",
NULL,
json_dev_rawrequest,
.dev_only = true,
},
};
int main(int argc, char *argv[])
{
setup_locale();
@ -1283,5 +1319,9 @@ int main(int argc, char *argv[])
commands, ARRAY_SIZE(commands),
notifications, ARRAY_SIZE(notifications),
hooks, ARRAY_SIZE(hooks),
NULL, 0, NULL);
NULL, 0,
plugin_option("fetchinvoice-noconnect", "flag",
"Don't try to connect directly to fetch/pay an invoice.",
flag_option, flag_jsonfmt, &disable_connect),
NULL);
}

View file

@ -4,6 +4,20 @@
struct command_result;
struct command;
struct plugin;
/* This is me. */
extern struct pubkey id;
/* Are offers enabled? */
extern bool offers_enabled;
/* --fetchinvoice-noconnect */
extern bool disable_connect;
/* --cltv-final */
extern u16 cltv_final;
/* Current header_count */
extern u32 blockheight;
/* Basis for invoice secrets */
extern struct secret invoicesecret_base;
/* If they give us an scid, do a lookup */
bool convert_to_scidd(struct command *cmd,
@ -14,4 +28,7 @@ struct command_result *WARN_UNUSED_RESULT
send_onion_reply(struct command *cmd,
struct blinded_path *reply_path,
struct tlv_onionmsg_tlv *payload);
/* Get the (latest) gossmap */
struct gossmap *get_gossmap(struct plugin *plugin);
#endif /* LIGHTNING_PLUGINS_OFFERS_H */

View file

@ -19,8 +19,6 @@
#include <secp256k1_schnorrsig.h>
#include <sodium.h>
static struct gossmap *global_gossmap;
/* We need to keep the reply path around so we can reply with invoice */
struct invreq {
struct tlv_invoice_request *invreq;
@ -101,31 +99,6 @@ fail_internalerr(struct command *cmd,
return ret;
}
static void init_gossmap(struct plugin *plugin)
{
size_t num_cupdates_rejected;
global_gossmap
= notleak_with_children(gossmap_load(NULL,
GOSSIP_STORE_FILENAME,
&num_cupdates_rejected));
if (!global_gossmap)
plugin_err(plugin, "Could not load gossmap %s: %s",
GOSSIP_STORE_FILENAME, strerror(errno));
if (num_cupdates_rejected)
plugin_log(plugin, LOG_DBG,
"gossmap ignored %zu channel updates",
num_cupdates_rejected);
}
static struct gossmap *get_gossmap(struct plugin *plugin)
{
if (!global_gossmap)
init_gossmap(plugin);
else
gossmap_refresh(global_gossmap, NULL);
return global_gossmap;
}
#define invreq_must_have(cmd_, ir_, fld_) \
test_field(cmd_, ir_, ir_->invreq->fld_ != NULL, #fld_, "missing")
#define invreq_must_not_have(cmd_, ir_, fld_) \

View file

@ -3,12 +3,6 @@
#include "config.h"
#include <plugins/libplugin.h>
extern u16 cltv_final;
extern u32 blockheight;
extern struct secret invoicesecret_base;
extern struct pubkey id;
extern bool disable_connect;
/* We got an onionmessage with an invreq! */
struct command_result *handle_invoice_request(struct command *cmd,
const u8 *invreqbin,

View file

@ -7,6 +7,7 @@
#include <common/json_param.h>
#include <common/json_stream.h>
#include <common/overflows.h>
#include <plugins/offers.h>
#include <plugins/offers_offer.h>
#include <sodium/randombytes.h>

View file

@ -3,9 +3,6 @@
#include "config.h"
#include <plugins/libplugin.h>
extern struct pubkey id;
extern bool offers_enabled;
struct command_result *json_offer(struct command *cmd,
const char *buffer,
const jsmntok_t *params);

View file

@ -4246,6 +4246,7 @@ def test_mpp_overload_payee(node_factory, bitcoind):
l1.rpc.pay(inv)
@unittest.skipIf(TEST_NETWORK != 'regtest', "Canned offer is network specific")
def test_offer_needs_option(node_factory):
"""Make sure we don't make offers without offer command"""
l1 = node_factory.get_node()
@ -4254,8 +4255,8 @@ def test_offer_needs_option(node_factory):
with pytest.raises(RpcError, match='experimental-offers not enabled'):
l1.rpc.call('invoicerequest', {'amount': '2msat',
'description': 'simple test'})
with pytest.raises(RpcError, match='Unknown command'):
l1.rpc.call('fetchinvoice', {'offer': 'aaaa'})
with pytest.raises(RpcError, match='experimental-offers not enabled'):
l1.rpc.call('fetchinvoice', {'offer': 'lno1qgsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcgqyqs5pr5v4ehg93pqfnwgkvdr57yzh6h92zg3qctvrm7w38djg67kzcm4yeg8vc4cq63s'})
# Decode still works though
assert l1.rpc.decode('lno1qgsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcgqyqs5pr5v4ehg93pqfnwgkvdr57yzh6h92zg3qctvrm7w38djg67kzcm4yeg8vc4cq63s')['valid']