core-lightning/common/test/run-route.c

313 lines
10 KiB
C

#include <assert.h>
#include <common/dijkstra.h>
#include <common/gossmap.h>
#include <common/gossip_store.h>
#include <common/route.h>
#include <common/type_to_string.h>
#include <bitcoin/chainparams.h>
#include <stdio.h>
#include <wire/peer_wiregen.h>
#include <unistd.h>
/* AUTOGENERATED MOCKS START */
/* Generated stub for fromwire_bigsize */
bigsize_t fromwire_bigsize(const u8 **cursor UNNEEDED, size_t *max UNNEEDED)
{ fprintf(stderr, "fromwire_bigsize called!\n"); abort(); }
/* Generated stub for fromwire_channel_id */
void fromwire_channel_id(const u8 **cursor UNNEEDED, size_t *max UNNEEDED,
struct channel_id *channel_id UNNEEDED)
{ fprintf(stderr, "fromwire_channel_id called!\n"); abort(); }
/* Generated stub for fromwire_tlv */
bool fromwire_tlv(const u8 **cursor UNNEEDED, size_t *max UNNEEDED,
const struct tlv_record_type *types UNNEEDED, size_t num_types UNNEEDED,
void *record UNNEEDED, struct tlv_field **fields UNNEEDED)
{ fprintf(stderr, "fromwire_tlv called!\n"); abort(); }
/* Generated stub for tlv_fields_valid */
bool tlv_fields_valid(const struct tlv_field *fields UNNEEDED, u64 *allow_extra UNNEEDED,
size_t *err_index UNNEEDED)
{ fprintf(stderr, "tlv_fields_valid called!\n"); abort(); }
/* Generated stub for towire_bigsize */
void towire_bigsize(u8 **pptr UNNEEDED, const bigsize_t val UNNEEDED)
{ fprintf(stderr, "towire_bigsize called!\n"); abort(); }
/* Generated stub for towire_channel_id */
void towire_channel_id(u8 **pptr UNNEEDED, const struct channel_id *channel_id UNNEEDED)
{ fprintf(stderr, "towire_channel_id called!\n"); abort(); }
/* Generated stub for towire_tlv */
void towire_tlv(u8 **pptr UNNEEDED,
const struct tlv_record_type *types UNNEEDED, size_t num_types UNNEEDED,
const void *record UNNEEDED)
{ fprintf(stderr, "towire_tlv called!\n"); abort(); }
/* Generated stub for type_to_string_ */
const char *type_to_string_(const tal_t *ctx UNNEEDED, const char *typename UNNEEDED,
union printable_types u UNNEEDED)
{ fprintf(stderr, "type_to_string_ called!\n"); abort(); }
/* AUTOGENERATED MOCKS END */
static void write_to_store(int store_fd, const u8 *msg)
{
struct gossip_hdr hdr;
hdr.len = cpu_to_be32(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 update_connection(int store_fd,
const struct node_id *from,
const struct node_id *to,
u32 base_fee, s32 proportional_fee,
u32 delay,
bool disable)
{
struct short_channel_id scid;
secp256k1_ecdsa_signature dummy_sig;
u8 *msg;
/* So valgrind doesn't complain */
memset(&dummy_sig, 0, sizeof(dummy_sig));
/* Make a unique scid. */
memcpy(&scid, from, sizeof(scid) / 2);
memcpy((char *)&scid + sizeof(scid) / 2, to, sizeof(scid) / 2);
msg = towire_channel_update_option_channel_htlc_max(tmpctx,
&dummy_sig,
&chainparams->genesis_blockhash,
&scid, 0,
ROUTING_OPT_HTLC_MAX_MSAT,
node_id_idx(from, to)
+ (disable ? ROUTING_FLAGS_DISABLED : 0),
delay,
AMOUNT_MSAT(0),
base_fee,
proportional_fee,
AMOUNT_MSAT(100000 * 1000));
write_to_store(store_fd, msg);
}
static void add_connection(int store_fd,
const struct node_id *from,
const struct node_id *to,
u32 base_fee, s32 proportional_fee,
u32 delay)
{
struct short_channel_id scid;
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);
/* Make a unique scid. */
memcpy(&scid, from, sizeof(scid) / 2);
memcpy((char *)&scid + sizeof(scid) / 2, to, sizeof(scid) / 2);
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);
update_connection(store_fd, from, to, base_fee, proportional_fee,
delay, false);
}
static bool channel_is_between(const struct gossmap *gossmap,
const struct route_hop *route,
const struct gossmap_node *a,
const struct gossmap_node *b)
{
const struct gossmap_chan *c = gossmap_find_chan(gossmap, &route->scid);
if (c->half[route->direction].nodeidx
!= gossmap_node_idx(gossmap, a))
return false;
if (c->half[!route->direction].nodeidx
!= gossmap_node_idx(gossmap, b))
return false;
return true;
}
/* route_can_carry disregards unless *both* dirs are enabled, so we use
* a simpler variant here */
static bool route_can_carry_unless_disabled(const struct gossmap *map,
const struct gossmap_chan *c,
int dir,
struct amount_msat amount,
void *arg)
{
if (!c->half[dir].enabled)
return false;
return route_can_carry_even_disabled(map, c, dir, amount, arg);
}
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);
}
int main(void)
{
setup_locale();
struct node_id a, b, c, d;
struct gossmap_node *a_node, *b_node, *c_node, *d_node;
struct privkey tmp;
const struct dijkstra *dij;
struct route_hop *route;
int store_fd;
struct gossmap *gossmap;
const double riskfactor = 1.0;
char gossip_version = GOSSIP_STORE_VERSION;
char gossipfilename[] = "/tmp/run-route-gossipstore.XXXXXX";
secp256k1_ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY
| SECP256K1_CONTEXT_SIGN);
setup_tmpctx();
chainparams = chainparams_for_network("regtest");
store_fd = mkstemp(gossipfilename);
assert(write(store_fd, &gossip_version, sizeof(gossip_version))
== sizeof(gossip_version));
gossmap = gossmap_load(tmpctx, gossipfilename);
memset(&tmp, 'a', sizeof(tmp));
node_id_from_privkey(&tmp, &a);
memset(&tmp, 'b', sizeof(tmp));
node_id_from_privkey(&tmp, &b);
assert(!gossmap_refresh(gossmap));
/* A<->B */
add_connection(store_fd, &a, &b, 1, 1, 1);
assert(gossmap_refresh(gossmap));
a_node = gossmap_find_node(gossmap, &a);
b_node = gossmap_find_node(gossmap, &b);
dij = dijkstra(tmpctx, gossmap, b_node, AMOUNT_MSAT(1000), riskfactor,
route_can_carry_unless_disabled,
route_score_cheaper, NULL);
route = route_from_dijkstra(tmpctx, gossmap, dij, a_node, AMOUNT_MSAT(1000), 10);
assert(route);
assert(tal_count(route) == 1);
assert(amount_msat_eq(route[0].amount, AMOUNT_MSAT(1000)));
assert(route[0].delay == 10);
/* A<->B<->C */
memset(&tmp, 'c', sizeof(tmp));
node_id_from_privkey(&tmp, &c);
add_connection(store_fd, &b, &c, 1, 1, 1);
assert(gossmap_refresh(gossmap));
/* These can theoretically change after refresh! */
a_node = gossmap_find_node(gossmap, &a);
b_node = gossmap_find_node(gossmap, &b);
c_node = gossmap_find_node(gossmap, &c);
dij = dijkstra(tmpctx, gossmap, c_node, AMOUNT_MSAT(1000), riskfactor,
route_can_carry_unless_disabled,
route_score_cheaper, NULL);
route = route_from_dijkstra(tmpctx, gossmap, dij, a_node,
AMOUNT_MSAT(1000), 11);
assert(route);
assert(tal_count(route) == 2);
assert(amount_msat_eq(route[1].amount, AMOUNT_MSAT(1000)));
assert(route[1].delay == 11);
assert(amount_msat_eq(route[0].amount, AMOUNT_MSAT(1001)));
assert(route[0].delay == 12);
/* A<->D<->C: Lower base, higher percentage. */
memset(&tmp, 'd', sizeof(tmp));
node_id_from_privkey(&tmp, &d);
add_connection(store_fd, &a, &d, 0, 2, 1);
add_connection(store_fd, &d, &c, 0, 2, 1);
assert(gossmap_refresh(gossmap));
/* These can theoretically change after refresh! */
a_node = gossmap_find_node(gossmap, &a);
b_node = gossmap_find_node(gossmap, &b);
c_node = gossmap_find_node(gossmap, &c);
d_node = gossmap_find_node(gossmap, &d);
/* Will go via D for small amounts. */
dij = dijkstra(tmpctx, gossmap, c_node, AMOUNT_MSAT(1000), riskfactor,
route_can_carry_unless_disabled,
route_score_cheaper, NULL);
route = route_from_dijkstra(tmpctx, gossmap, dij, a_node,
AMOUNT_MSAT(1000), 12);
assert(route);
assert(tal_count(route) == 2);
assert(channel_is_between(gossmap, &route[0], a_node, d_node));
assert(channel_is_between(gossmap, &route[1], d_node, c_node));
assert(amount_msat_eq(route[1].amount, AMOUNT_MSAT(1000)));
assert(route[1].delay == 12);
assert(amount_msat_eq(route[0].amount, AMOUNT_MSAT(1000)));
assert(route[0].delay == 13);
/* Will go via B for large amounts. */
dij = dijkstra(tmpctx, gossmap, c_node, AMOUNT_MSAT(3000000), riskfactor,
route_can_carry_unless_disabled,
route_score_cheaper, NULL);
route = route_from_dijkstra(tmpctx, gossmap, dij, a_node,
AMOUNT_MSAT(3000000), 13);
assert(route);
assert(tal_count(route) == 2);
assert(channel_is_between(gossmap, &route[0], a_node, b_node));
assert(channel_is_between(gossmap, &route[1], b_node, c_node));
assert(amount_msat_eq(route[1].amount, AMOUNT_MSAT(3000000)));
assert(route[1].delay == 13);
assert(amount_msat_eq(route[0].amount, AMOUNT_MSAT(3000000 + 3 + 1)));
assert(route[0].delay == 14);
/* Make B->C inactive, force it back via D */
update_connection(store_fd, &b, &c, 1, 1, 1, true);
assert(gossmap_refresh(gossmap));
/* These can theoretically change after refresh! */
a_node = gossmap_find_node(gossmap, &a);
b_node = gossmap_find_node(gossmap, &b);
c_node = gossmap_find_node(gossmap, &c);
d_node = gossmap_find_node(gossmap, &d);
dij = dijkstra(tmpctx, gossmap, c_node, AMOUNT_MSAT(3000000), riskfactor,
route_can_carry_unless_disabled,
route_score_cheaper, NULL);
route = route_from_dijkstra(tmpctx, gossmap, dij, a_node,
AMOUNT_MSAT(3000000), 14);
assert(route);
assert(tal_count(route) == 2);
assert(channel_is_between(gossmap, &route[0], a_node, d_node));
assert(channel_is_between(gossmap, &route[1], d_node, c_node));
assert(amount_msat_eq(route[1].amount, AMOUNT_MSAT(3000000)));
assert(route[1].delay == 14);
assert(amount_msat_eq(route[0].amount, AMOUNT_MSAT(3000000 + 6)));
assert(route[0].delay == 15);
tal_free(tmpctx);
secp256k1_context_destroy(secp256k1_ctx);
return 0;
}