pytest: test fetchinvoice reply path which is not a direct peer.

Our fetchinvoice always creates a reply path which terminates at their peer,
so we need a dev overrride for that.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell 2024-05-14 14:01:26 +09:30
parent 8714289c8c
commit 9aed594177
2 changed files with 60 additions and 5 deletions

View file

@ -42,6 +42,9 @@ struct sent {
/* When creating blinded return path, use scid not pubkey for intro node. */
struct short_channel_id_dir *dev_path_use_scidd;
/* Force reply path, for testing. */
struct pubkey *dev_reply_path;
/* The invreq we sent, OR the invoice we sent */
struct tlv_invoice_request *invreq;
@ -603,11 +606,15 @@ static struct command_result *make_reply_path(struct command *cmd,
sending->sent->reply_secret = tal(sending->sent, struct secret);
randombytes_buf(sending->sent->reply_secret, sizeof(struct secret));
/* FIXME: Could create an independent reply path, not just
* reverse existing. */
ids = tal_arr(tmpctx, struct pubkey, nhops - 1);
for (int i = nhops - 2; i >= 0; i--)
ids[nhops - 2 - i] = sending->sent->path[i];
if (sending->sent->dev_reply_path) {
ids = sending->sent->dev_reply_path;
} else {
/* FIXME: Could create an independent reply path, not just
* reverse existing. */
ids = tal_arr(tmpctx, struct pubkey, nhops - 1);
for (int i = nhops - 2; i >= 0; i--)
ids[nhops - 2 - i] = sending->sent->path[i];
}
rpath = blinded_path(cmd, cmd, ids, sending->sent->dev_path_use_scidd,
sending->sent->reply_secret);
@ -802,6 +809,29 @@ static struct command_result *param_dev_scidd(struct command *cmd, const char *n
"should be a short_channel_id of form NxNxN/dir");
}
static struct command_result *param_dev_reply_path(struct command *cmd, const char *name,
const char *buffer, const jsmntok_t *tok,
struct pubkey **path)
{
size_t i;
const jsmntok_t *t;
if (!plugin_developer_mode(cmd->plugin))
return command_fail_badparam(cmd, name, buffer, tok,
"not available outside --developer mode");
if (tok->type != JSMN_ARRAY)
return command_fail_badparam(cmd, name, buffer, tok, "Must be array");
*path = tal_arr(cmd, struct pubkey, tok->size);
json_for_each_arr(i, t, tok) {
if (!json_to_pubkey(buffer, t, &(*path)[i]))
return command_fail_badparam(cmd, name, buffer, t, "invalid pubkey");
}
return NULL;
}
/* Fetches an invoice for this offer, and makes sure it corresponds. */
static struct command_result *json_fetchinvoice(struct command *cmd,
const char *buffer,
@ -826,6 +856,7 @@ static struct command_result *json_fetchinvoice(struct command *cmd,
p_opt_def("timeout", param_number, &timeout, 60),
p_opt("payer_note", param_string, &payer_note),
p_opt("dev_path_use_scidd", param_dev_scidd, &sent->dev_path_use_scidd),
p_opt("dev_reply_path", param_dev_reply_path, &sent->dev_reply_path),
NULL))
return command_param_failed();
@ -1274,6 +1305,7 @@ static struct command_result *json_sendinvoice(struct command *cmd,
return command_param_failed();
sent->dev_path_use_scidd = NULL;
sent->dev_reply_path = NULL;
/* BOLT-offers #12:
* - if the invoice is in response to an `invoice_request`:
@ -1392,6 +1424,7 @@ static struct command_result *json_dev_rawrequest(struct command *cmd,
sent->cmd = cmd;
sent->offer = NULL;
sent->dev_path_use_scidd = NULL;
sent->dev_reply_path = NULL;
return establish_onion_path(cmd, get_gossmap(cmd->plugin), &local_id,
node_id,

View file

@ -4731,6 +4731,28 @@ def test_fetchinvoice_autoconnect(node_factory, bitcoind):
assert l3.rpc.listpeers(l2.info['id'])['peers'] != []
def test_fetchinvoice_disconnected_reply(node_factory, bitcoind):
"""We ask for invoice, but reply path doesn't lead directly from recipient"""
l1, l2, l3 = node_factory.get_nodes(3,
opts={'experimental-offers': None,
'may_reconnect': True,
'dev-no-reconnect': None,
'dev-allow-localhost': None})
l3.rpc.connect(l2.info['id'], 'localhost', l2.port)
# Make l1, l2 public (so l3 can auto connect).
node_factory.join_nodes([l1, l2], wait_for_announce=True)
wait_for(lambda: l3.rpc.listnodes(l1.info['id'])['nodes'] != [])
offer = l3.rpc.offer(amount='5msat', description='test_fetchinvoice_disconnected_reply')
# l2 sets reply_path to be l1->l2, l3 will connect to l1 to send reply.
# FIXME: if code were smarter, it would simply send via l2, and this test
# would have to get more sophisticated!
l2.rpc.fetchinvoice(offer=offer['bolt12'], dev_reply_path=[l1.info['id'], l2.info['id']])
assert only_one(l1.rpc.listpeers(l3.info['id'])['peers'])
def test_pay_blockheight_mismatch(node_factory, bitcoind):
"""Test that we can send a payment even if not caught up with the chain.