renepay: test channel capacity unavailable

Add a test that checks `get_route` and `uncertainty_update` behaviour's
handling of missing channel capacities.

Signed-off-by: Lagrang3 <lagrang3@protonmail.com>
This commit is contained in:
Lagrang3 2024-05-14 07:26:38 +01:00 committed by Alex Myers
parent d30b1c8dc2
commit 4b8d9116de
4 changed files with 305 additions and 112 deletions

View file

@ -10,9 +10,14 @@ $(PLUGIN_RENEPAY_TEST_OBJS): $(PLUGIN_RENEPAY_SRC)
PLUGIN_RENEPAY_TEST_COMMON_OBJS := \
plugins/renepay/dijkstra.o \
plugins/renepay/chan_extra.o
plugins/renepay/chan_extra.o \
bitcoin/chainparams.o \
common/gossmap.o \
common/fp16.o \
common/dijkstra.o \
gossipd/gossip_store_wiregen.o
$(PLUGIN_RENEPAY_TEST_PROGRAMS): $(PLUGIN_RENEPAY_TEST_COMMON_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) $(CCAN_OBJS) bitcoin/chainparams.o common/gossmap.o common/fp16.o common/dijkstra.o gossipd/gossip_store_wiregen.o
$(PLUGIN_RENEPAY_TEST_PROGRAMS): $(PLUGIN_RENEPAY_TEST_COMMON_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) $(CCAN_OBJS)
check-renepay: $(PLUGIN_RENEPAY_TEST_PROGRAMS:%=unittest/%)

View file

@ -0,0 +1,110 @@
#ifndef LIGHTNING_PLUGINS_RENEPAY_TEST_COMMON_H
#define LIGHTNING_PLUGINS_RENEPAY_TEST_COMMON_H
#include "config.h"
#include <common/gossip_store.h>
#include <gossipd/gossip_store_wiregen.h>
#include <stdio.h>
#include <unistd.h>
#include <wire/peer_wiregen.h>
static const char *print_routes(const tal_t *ctx,
struct route **routes)
{
tal_t *this_ctx = tal(ctx, tal_t);
char *buff = tal_fmt(ctx, "%zu routes\n", tal_count(routes));
for (size_t i = 0; i < tal_count(routes); i++) {
struct amount_msat fee, delivered;
delivered = route_delivers(routes[i]);
fee = route_fees(routes[i]);
tal_append_fmt(&buff, " %s", fmt_route_path(this_ctx, routes[i]));
tal_append_fmt(&buff, " %s delivered with fee %s\n",
fmt_amount_msat(this_ctx, delivered),
fmt_amount_msat(this_ctx, fee));
}
tal_free(this_ctx);
return buff;
}
static void write_to_store(int store_fd, const u8 *msg)
{
struct gossip_hdr hdr;
hdr.flags = cpu_to_be16(0);
hdr.len = cpu_to_be16(tal_count(msg));
/* We don't actually check these! */
hdr.crc = 0;
hdr.timestamp = 0;
assert(write(store_fd, &hdr, sizeof(hdr)) == sizeof(hdr));
assert(write(store_fd, msg, tal_count(msg)) == tal_count(msg));
}
static void add_connection(int store_fd,
const struct node_id *from,
const struct node_id *to,
struct short_channel_id scid,
struct amount_msat min,
struct amount_msat max,
u32 base_fee, s32 proportional_fee,
u32 delay,
struct amount_sat capacity,
bool add_capacity)
{
secp256k1_ecdsa_signature dummy_sig;
struct secret not_a_secret;
struct pubkey dummy_key;
u8 *msg;
const struct node_id *ids[2];
/* So valgrind doesn't complain */
memset(&dummy_sig, 0, sizeof(dummy_sig));
memset(&not_a_secret, 1, sizeof(not_a_secret));
pubkey_from_secret(&not_a_secret, &dummy_key);
if (node_id_cmp(from, to) > 0) {
ids[0] = to;
ids[1] = from;
} else {
ids[0] = from;
ids[1] = to;
}
msg = towire_channel_announcement(tmpctx, &dummy_sig, &dummy_sig,
&dummy_sig, &dummy_sig,
/* features */ NULL,
&chainparams->genesis_blockhash,
scid,
ids[0], ids[1],
&dummy_key, &dummy_key);
write_to_store(store_fd, msg);
if (add_capacity) {
msg = towire_gossip_store_channel_amount(tmpctx, capacity);
write_to_store(store_fd, msg);
}
u8 flags = node_id_idx(from, to);
msg = towire_channel_update(tmpctx,
&dummy_sig,
&chainparams->genesis_blockhash,
scid, 0,
ROUTING_OPT_HTLC_MAX_MSAT,
flags,
delay,
min,
base_fee,
proportional_fee,
max);
write_to_store(store_fd, msg);
}
static void node_id_from_privkey(const struct privkey *p, struct node_id *id)
{
struct pubkey k;
pubkey_from_privkey(p, &k);
node_id_from_pubkey(id, &k);
}
#endif /* LIGHTNING_PLUGINS_RENEPAY_TEST_COMMON_H */

View file

@ -1,3 +1,5 @@
/* Checks that get_route can handle bottleneck situations assigning values to
* routes that do not exceed the liquidity constraints. */
#include "config.h"
#include "../errorcodes.c"
@ -7,18 +9,14 @@
#include "../disabledmap.c"
#include "../route.c"
#include "../routebuilder.c"
#include "common.h"
#include <bitcoin/chainparams.h>
#include <bitcoin/preimage.h>
#include <ccan/str/hex/hex.h>
#include <common/gossip_store.h>
#include <common/setup.h>
#include <common/utils.h>
#include <gossipd/gossip_store_wiregen.h>
#include <sodium/randombytes.h>
#include <stdio.h>
#include <unistd.h>
#include <wire/peer_wiregen.h>
static u8 empty_map[] = {10};
@ -56,102 +54,6 @@ static const char *print_flows(const tal_t *ctx, const char *desc,
return buff;
}
static const char *print_routes(const tal_t *ctx,
struct route **routes)
{
tal_t *this_ctx = tal(ctx, tal_t);
char *buff = tal_fmt(ctx, "%zu routes\n", tal_count(routes));
for (size_t i = 0; i < tal_count(routes); i++) {
struct amount_msat fee, delivered;
delivered = route_delivers(routes[i]);
fee = route_fees(routes[i]);
tal_append_fmt(&buff, " %s", fmt_route_path(this_ctx, routes[i]));
tal_append_fmt(&buff, " %s delivered with fee %s\n",
fmt_amount_msat(this_ctx, delivered),
fmt_amount_msat(this_ctx, fee));
}
tal_free(this_ctx);
return buff;
}
static void write_to_store(int store_fd, const u8 *msg)
{
struct gossip_hdr hdr;
hdr.flags = cpu_to_be16(0);
hdr.len = cpu_to_be16(tal_count(msg));
/* We don't actually check these! */
hdr.crc = 0;
hdr.timestamp = 0;
assert(write(store_fd, &hdr, sizeof(hdr)) == sizeof(hdr));
assert(write(store_fd, msg, tal_count(msg)) == tal_count(msg));
}
static void add_connection(int store_fd,
const struct node_id *from,
const struct node_id *to,
struct short_channel_id scid,
struct amount_msat min,
struct amount_msat max,
u32 base_fee, s32 proportional_fee,
u32 delay,
struct amount_sat capacity)
{
secp256k1_ecdsa_signature dummy_sig;
struct secret not_a_secret;
struct pubkey dummy_key;
u8 *msg;
const struct node_id *ids[2];
/* So valgrind doesn't complain */
memset(&dummy_sig, 0, sizeof(dummy_sig));
memset(&not_a_secret, 1, sizeof(not_a_secret));
pubkey_from_secret(&not_a_secret, &dummy_key);
if (node_id_cmp(from, to) > 0) {
ids[0] = to;
ids[1] = from;
} else {
ids[0] = from;
ids[1] = to;
}
msg = towire_channel_announcement(tmpctx, &dummy_sig, &dummy_sig,
&dummy_sig, &dummy_sig,
/* features */ NULL,
&chainparams->genesis_blockhash,
scid,
ids[0], ids[1],
&dummy_key, &dummy_key);
write_to_store(store_fd, msg);
msg = towire_gossip_store_channel_amount(tmpctx, capacity);
write_to_store(store_fd, msg);
u8 flags = node_id_idx(from, to);
msg = towire_channel_update(tmpctx,
&dummy_sig,
&chainparams->genesis_blockhash,
scid, 0,
ROUTING_OPT_HTLC_MAX_MSAT,
flags,
delay,
min,
base_fee,
proportional_fee,
max);
write_to_store(store_fd, msg);
}
static void node_id_from_privkey(const struct privkey *p, struct node_id *id)
{
struct pubkey k;
pubkey_from_privkey(p, &k);
node_id_from_pubkey(id, &k);
}
#define NUM_NODES 8
int main(int argc, char *argv[])
@ -201,28 +103,32 @@ int main(int argc, char *argv[])
AMOUNT_MSAT(0),
AMOUNT_MSAT(60 * 1000 * 1000),
0, 0, 5,
AMOUNT_SAT(60 * 1000));
AMOUNT_SAT(60 * 1000),
true);
assert(mk_short_channel_id(&scid, 1, 3, 0));
add_connection(fd, &nodes[0], &nodes[2], scid,
AMOUNT_MSAT(0),
AMOUNT_MSAT(60 * 1000 * 1000),
0, 0, 5,
AMOUNT_SAT(60 * 1000));
AMOUNT_SAT(60 * 1000),
true);
assert(mk_short_channel_id(&scid, 2, 4, 0));
add_connection(fd, &nodes[1], &nodes[3], scid,
AMOUNT_MSAT(0),
AMOUNT_MSAT(1000 * 1000 * 1000),
0, 0, 5,
AMOUNT_SAT(1000 * 1000));
AMOUNT_SAT(1000 * 1000),
true);
assert(mk_short_channel_id(&scid, 3, 4, 0));
add_connection(fd, &nodes[2], &nodes[3], scid,
AMOUNT_MSAT(0),
AMOUNT_MSAT(1000 * 1000 * 1000),
0, 0, 5,
AMOUNT_SAT(1000 * 1000));
AMOUNT_SAT(1000 * 1000),
true);
assert(mk_short_channel_id(&scid, 4, 5, 0));
add_connection(fd, &nodes[3], &nodes[4], scid,
@ -232,35 +138,40 @@ int main(int argc, char *argv[])
* through this channel. */
AMOUNT_MSAT(106 * 1000 * 1000),
0, 0, 5,
AMOUNT_SAT(110 * 1000));
AMOUNT_SAT(110 * 1000),
true);
assert(mk_short_channel_id(&scid, 5, 6, 0));
add_connection(fd, &nodes[4], &nodes[5], scid,
AMOUNT_MSAT(0),
AMOUNT_MSAT(1000 * 1000 * 1000),
0, 100 * 1000 /* 10% */, 5,
AMOUNT_SAT(1000 * 1000));
AMOUNT_SAT(1000 * 1000),
true);
assert(mk_short_channel_id(&scid, 5, 7, 0));
add_connection(fd, &nodes[4], &nodes[6], scid,
AMOUNT_MSAT(0),
AMOUNT_MSAT(1000 * 1000 * 1000),
0, 100 * 1000 /* 10% */, 5,
AMOUNT_SAT(1000 * 1000));
AMOUNT_SAT(1000 * 1000),
true);
assert(mk_short_channel_id(&scid, 6, 8, 0));
add_connection(fd, &nodes[5], &nodes[7], scid,
AMOUNT_MSAT(0),
AMOUNT_MSAT(1000 * 1000 * 1000),
0, 0, 5,
AMOUNT_SAT(1000 * 1000));
AMOUNT_SAT(1000 * 1000),
true);
assert(mk_short_channel_id(&scid, 7, 8, 0));
add_connection(fd, &nodes[6], &nodes[7], scid,
AMOUNT_MSAT(0),
AMOUNT_MSAT(1000 * 1000 * 1000),
0, 0, 5,
AMOUNT_SAT(1000 * 1000));
AMOUNT_SAT(1000 * 1000),
true);
assert(gossmap_refresh(gossmap, NULL));
struct uncertainty *uncertainty = uncertainty_new(tmpctx);

View file

@ -0,0 +1,167 @@
/* Checks that uncertainty_update and get_routes can handle a gossmap where the
* capacity of some channels are missing.
*
* */
#include "config.h"
#include "../disabledmap.c"
#include "../errorcodes.c"
#include "../flow.c"
#include "../mcf.c"
#include "../route.c"
#include "../routebuilder.c"
#include "../uncertainty.c"
#include "common.h"
#include <bitcoin/chainparams.h>
#include <bitcoin/preimage.h>
#include <ccan/str/hex/hex.h>
#include <common/setup.h>
#include <common/utils.h>
#include <sodium/randombytes.h>
static u8 empty_map[] = {10};
#define NUM_NODES 4
int main(int argc, char *argv[])
{
int fd;
char *gossfile;
struct gossmap *gossmap;
struct node_id nodes[NUM_NODES];
common_setup(argv[0]);
chainparams = chainparams_for_network("regtest");
fd = tmpdir_mkstemp(tmpctx, "run-missingcapacity.XXXXXX", &gossfile);
assert(write(fd, empty_map, sizeof(empty_map)) == sizeof(empty_map));
gossmap = gossmap_load(tmpctx, gossfile, NULL);
assert(gossmap);
for (size_t i = 0; i < NUM_NODES; i++) {
struct privkey tmp;
memset(&tmp, i+1, sizeof(tmp));
node_id_from_privkey(&tmp, &nodes[i]);
}
/* We will try a payment from 1 to 4.
* There are two possible routes 1->2->4 or 1->3->4.
* However, we will simulate that we don't have channel 3->4's capacity
* in the gossmap (see #7194). We expect that 3->4 it's simply ignored
* and only route through 1->2->4 is used.
*
* +--2--+
* | |
* 1 4
* | |
* +--3--+
*
* */
struct short_channel_id scid;
assert(mk_short_channel_id(&scid, 1, 2, 0));
add_connection(fd, &nodes[0], &nodes[1], scid,
AMOUNT_MSAT(0),
AMOUNT_MSAT(1000 * 1000 * 1000),
0, 0, 5,
AMOUNT_SAT(1000 * 1000),
/* add capacity? = */ true);
assert(mk_short_channel_id(&scid, 2, 4, 0));
add_connection(fd, &nodes[1], &nodes[3], scid,
AMOUNT_MSAT(0),
AMOUNT_MSAT(1000 * 1000 * 1000),
0, 0, 5,
AMOUNT_SAT(1000 * 1000),
/* add capacity? = */ true);
assert(mk_short_channel_id(&scid, 1, 3, 0));
add_connection(fd, &nodes[0], &nodes[2], scid,
AMOUNT_MSAT(0),
AMOUNT_MSAT(1000 * 1000 * 1000),
0, 0, 5,
AMOUNT_SAT(1000 * 1000),
/* add capacity? = */ true);
assert(mk_short_channel_id(&scid, 3, 4, 0));
add_connection(fd, &nodes[2], &nodes[3], scid,
AMOUNT_MSAT(0),
AMOUNT_MSAT(1000 * 1000 * 1000),
0, 0, 5,
AMOUNT_SAT(1000 * 1000),
/* add capacity? = */ false);
assert(gossmap_refresh(gossmap, NULL));
struct uncertainty *uncertainty = uncertainty_new(tmpctx);
int skipped_count =
uncertainty_update(uncertainty, gossmap);
assert(skipped_count==1);
struct preimage preimage;
struct amount_msat maxfee = AMOUNT_MSAT(20*1000);
struct payment_info pinfo;
pinfo.invstr = NULL;
pinfo.label = NULL;
pinfo.description = NULL;
pinfo.payment_secret = NULL;
pinfo.payment_metadata = NULL;
pinfo.routehints = NULL;
pinfo.destination = nodes[3];
pinfo.amount = AMOUNT_MSAT(100 * 1000 * 1000);
assert(amount_msat_add(&pinfo.maxspend, maxfee, pinfo.amount));
pinfo.maxdelay = 100;
pinfo.final_cltv = 5;
pinfo.start_time = time_now();
pinfo.stop_time = timeabs_add(pinfo.start_time, time_from_sec(10000));
pinfo.base_fee_penalty = 1e-5;
pinfo.prob_cost_factor = 1e-5;
pinfo.delay_feefactor = 1e-6;
pinfo.min_prob_success = 0.9;
pinfo.use_shadow = false;
randombytes_buf(&preimage, sizeof(preimage));
sha256(&pinfo.payment_hash, &preimage, sizeof(preimage));
struct disabledmap *disabledmap = disabledmap_new(tmpctx);
enum jsonrpc_errcode errcode;
const char *err_msg;
u64 groupid = 1;
u64 next_partid=1;
struct route **routes = get_routes(
/* ctx */tmpctx,
/* payment */&pinfo,
/* source */&nodes[0],
/* destination */&nodes[3],
/* gossmap */gossmap,
/* uncertainty */uncertainty,
disabledmap,
/* amount */ pinfo.amount,
/* feebudget */maxfee,
&next_partid,
groupid,
&errcode,
&err_msg);
assert(routes);
if (!routes) {
printf("get_route failed with error %d: %s", errcode, err_msg);
} else {
printf("get_routes: %s\n", print_routes(tmpctx, routes));
assert(tal_count(routes) == 1);
assert(tal_count(routes[0]->hops) == 2);
assert(node_id_eq(&routes[0]->hops[0].node_id, &nodes[1]));
assert(node_id_eq(&routes[0]->hops[1].node_id, &nodes[3]));
}
common_shutdown();
}