From 7ef6dd04d96840eca7591cd3626f325ddf11a1ea Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Sat, 22 May 2021 14:30:15 +0930 Subject: [PATCH] plugins/test/run-route-overlong: modern overlong routing test. gossmap equivalent of gossipd/test/run-overlong.c. Signed-off-by: Rusty Russell --- plugins/test/Makefile | 7 + plugins/test/Makefile2 | 21 ++ plugins/test/run-route-overlong.c | 409 ++++++++++++++++++++++++++++++ 3 files changed, 437 insertions(+) create mode 100644 plugins/test/Makefile2 create mode 100644 plugins/test/run-route-overlong.c diff --git a/plugins/test/Makefile b/plugins/test/Makefile index 694ea7f7a..a65ae8b11 100644 --- a/plugins/test/Makefile +++ b/plugins/test/Makefile @@ -14,6 +14,13 @@ PLUGIN_TEST_COMMON_OBJS := \ common/type_to_string.o \ common/utils.o +plugins/test/run-route-overlong: \ + common/dijkstra.o \ + common/fp16.o \ + common/gossmap.o \ + common/node_id.o \ + common/route.o + $(PLUGIN_TEST_PROGRAMS): $(CCAN_OBJS) $(BITCOIN_OBJS) $(WIRE_OBJS) $(PLUGIN_TEST_COMMON_OBJS) $(PLUGIN_TEST_OBJS): $(PLUGIN_FUNDER_HEADER) $(PLUGIN_FUNDER_SRC) diff --git a/plugins/test/Makefile2 b/plugins/test/Makefile2 new file mode 100644 index 000000000..39a27f183 --- /dev/null +++ b/plugins/test/Makefile2 @@ -0,0 +1,21 @@ +# Note that these actually #include everything they need, except ccan/ and bitcoin/. +# That allows for unit testing of statics, and special effects. +PLUGIN_TEST_SRC := $(wildcard plugins/test/run-*.c) +PLUGIN_TEST_OBJS := $(PLUGIN_TEST_SRC:.c=.o) +PLUGIN_TEST_PROGRAMS := $(PLUGIN_TEST_OBJS:.o=) + +ALL_C_SOURCES += $(PLUGIN_TEST_SRC) +ALL_TEST_PROGRAMS += $(PLUGIN_TEST_PROGRAMS) + +update-mocks: $(PLUGIN_TEST_SRC:%=update-mocks/%) + +$(PLUGIN_TEST_PROGRAMS): $(CCAN_OBJS) $(BITCOIN_OBJS) $(WIRE_OBJS) $(PLUGIN_COMMON_OBJS) + +$(PLUGIN_TEST_OBJS): $(PLUGIN_ALL_HEADERS) $(PLUGIN_ALL_SRC) + +check-units: $(PLUGIN_TEST_PROGRAMS:%=unittest/%) + +plugins/test/run-route-overlong: \ + common/dijkstra.o \ + common/gossmap.o \ + common/route.o diff --git a/plugins/test/run-route-overlong.c b/plugins/test/run-route-overlong.c new file mode 100644 index 000000000..23c235e2a --- /dev/null +++ b/plugins/test/run-route-overlong.c @@ -0,0 +1,409 @@ +#include "../libplugin-pay.c" +#include +#include +#include +#include +#include + +/* AUTOGENERATED MOCKS START */ +/* Generated stub for command_finished */ +struct command_result *command_finished(struct command *cmd UNNEEDED, struct json_stream *response UNNEEDED) +{ fprintf(stderr, "command_finished called!\n"); abort(); } +/* Generated stub for command_still_pending */ +struct command_result *command_still_pending(struct command *cmd UNNEEDED) +{ fprintf(stderr, "command_still_pending called!\n"); abort(); } +/* Generated stub for feature_offered */ +bool feature_offered(const u8 *features UNNEEDED, size_t f UNNEEDED) +{ fprintf(stderr, "feature_offered called!\n"); abort(); } +/* 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 json_add_amount_msat_compat */ +void json_add_amount_msat_compat(struct json_stream *result UNNEEDED, + struct amount_msat msat UNNEEDED, + const char *rawfieldname UNNEEDED, + const char *msatfieldname) + +{ fprintf(stderr, "json_add_amount_msat_compat called!\n"); abort(); } +/* Generated stub for json_add_amount_msat_only */ +void json_add_amount_msat_only(struct json_stream *result UNNEEDED, + const char *msatfieldname UNNEEDED, + struct amount_msat msat) + +{ fprintf(stderr, "json_add_amount_msat_only called!\n"); abort(); } +/* Generated stub for json_add_hex_talarr */ +void json_add_hex_talarr(struct json_stream *result UNNEEDED, + const char *fieldname UNNEEDED, + const tal_t *data UNNEEDED) +{ fprintf(stderr, "json_add_hex_talarr called!\n"); abort(); } +/* Generated stub for json_add_invstring */ +void json_add_invstring(struct json_stream *result UNNEEDED, const char *invstring UNNEEDED) +{ fprintf(stderr, "json_add_invstring called!\n"); abort(); } +/* Generated stub for json_add_node_id */ +void json_add_node_id(struct json_stream *response UNNEEDED, + const char *fieldname UNNEEDED, + const struct node_id *id UNNEEDED) +{ fprintf(stderr, "json_add_node_id called!\n"); abort(); } +/* Generated stub for json_add_num */ +void json_add_num(struct json_stream *result UNNEEDED, const char *fieldname UNNEEDED, + unsigned int value UNNEEDED) +{ fprintf(stderr, "json_add_num called!\n"); abort(); } +/* Generated stub for json_add_preimage */ +void json_add_preimage(struct json_stream *result UNNEEDED, const char *fieldname UNNEEDED, + const struct preimage *preimage UNNEEDED) +{ fprintf(stderr, "json_add_preimage called!\n"); abort(); } +/* Generated stub for json_add_secret */ +void json_add_secret(struct json_stream *response UNNEEDED, + const char *fieldname UNNEEDED, + const struct secret *secret UNNEEDED) +{ fprintf(stderr, "json_add_secret called!\n"); abort(); } +/* Generated stub for json_add_sha256 */ +void json_add_sha256(struct json_stream *result UNNEEDED, const char *fieldname UNNEEDED, + const struct sha256 *hash UNNEEDED) +{ fprintf(stderr, "json_add_sha256 called!\n"); abort(); } +/* Generated stub for json_add_short_channel_id */ +void json_add_short_channel_id(struct json_stream *response UNNEEDED, + const char *fieldname UNNEEDED, + const struct short_channel_id *id UNNEEDED) +{ fprintf(stderr, "json_add_short_channel_id called!\n"); abort(); } +/* Generated stub for json_add_string */ +void json_add_string(struct json_stream *result UNNEEDED, const char *fieldname UNNEEDED, const char *value UNNEEDED) +{ fprintf(stderr, "json_add_string called!\n"); abort(); } +/* Generated stub for json_add_timeabs */ +void json_add_timeabs(struct json_stream *result UNNEEDED, const char *fieldname UNNEEDED, + struct timeabs t UNNEEDED) +{ fprintf(stderr, "json_add_timeabs called!\n"); abort(); } +/* Generated stub for json_add_u32 */ +void json_add_u32(struct json_stream *result UNNEEDED, const char *fieldname UNNEEDED, + uint32_t value UNNEEDED) +{ fprintf(stderr, "json_add_u32 called!\n"); abort(); } +/* Generated stub for json_add_u64 */ +void json_add_u64(struct json_stream *result UNNEEDED, const char *fieldname UNNEEDED, + uint64_t value UNNEEDED) +{ fprintf(stderr, "json_add_u64 called!\n"); abort(); } +/* Generated stub for json_array_end */ +void json_array_end(struct json_stream *js UNNEEDED) +{ fprintf(stderr, "json_array_end called!\n"); abort(); } +/* Generated stub for json_array_start */ +void json_array_start(struct json_stream *js UNNEEDED, const char *fieldname UNNEEDED) +{ fprintf(stderr, "json_array_start called!\n"); abort(); } +/* Generated stub for json_get_member */ +const jsmntok_t *json_get_member(const char *buffer UNNEEDED, const jsmntok_t tok[] UNNEEDED, + const char *label UNNEEDED) +{ fprintf(stderr, "json_get_member called!\n"); abort(); } +/* Generated stub for json_next */ +const jsmntok_t *json_next(const jsmntok_t *tok UNNEEDED) +{ fprintf(stderr, "json_next called!\n"); abort(); } +/* Generated stub for json_object_end */ +void json_object_end(struct json_stream *js UNNEEDED) +{ fprintf(stderr, "json_object_end called!\n"); abort(); } +/* Generated stub for json_object_start */ +void json_object_start(struct json_stream *ks UNNEEDED, const char *fieldname UNNEEDED) +{ fprintf(stderr, "json_object_start called!\n"); abort(); } +/* Generated stub for json_strdup */ +char *json_strdup(const tal_t *ctx UNNEEDED, const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED) +{ fprintf(stderr, "json_strdup called!\n"); abort(); } +/* Generated stub for json_to_bool */ +bool json_to_bool(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, bool *b UNNEEDED) +{ fprintf(stderr, "json_to_bool called!\n"); abort(); } +/* Generated stub for json_to_createonion_response */ +struct createonion_response *json_to_createonion_response(const tal_t *ctx UNNEEDED, + const char *buffer UNNEEDED, + const jsmntok_t *toks UNNEEDED) +{ fprintf(stderr, "json_to_createonion_response called!\n"); abort(); } +/* Generated stub for json_to_int */ +bool json_to_int(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, int *num UNNEEDED) +{ fprintf(stderr, "json_to_int called!\n"); abort(); } +/* Generated stub for json_to_listpeers_result */ +struct listpeers_result *json_to_listpeers_result(const tal_t *ctx UNNEEDED, + const char *buffer UNNEEDED, + const jsmntok_t *tok UNNEEDED) +{ fprintf(stderr, "json_to_listpeers_result called!\n"); abort(); } +/* Generated stub for json_to_msat */ +bool json_to_msat(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, + struct amount_msat *msat UNNEEDED) +{ fprintf(stderr, "json_to_msat called!\n"); abort(); } +/* Generated stub for json_to_node_id */ +bool json_to_node_id(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, + struct node_id *id UNNEEDED) +{ fprintf(stderr, "json_to_node_id called!\n"); abort(); } +/* Generated stub for json_to_number */ +bool json_to_number(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, + unsigned int *num UNNEEDED) +{ fprintf(stderr, "json_to_number called!\n"); abort(); } +/* Generated stub for json_to_preimage */ +bool json_to_preimage(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, struct preimage *preimage UNNEEDED) +{ fprintf(stderr, "json_to_preimage called!\n"); abort(); } +/* Generated stub for json_to_sat */ +bool json_to_sat(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, + struct amount_sat *sat UNNEEDED) +{ fprintf(stderr, "json_to_sat called!\n"); abort(); } +/* Generated stub for json_to_short_channel_id */ +bool json_to_short_channel_id(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, + struct short_channel_id *scid UNNEEDED) +{ fprintf(stderr, "json_to_short_channel_id called!\n"); abort(); } +/* Generated stub for json_to_u16 */ +bool json_to_u16(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, + uint16_t *num UNNEEDED) +{ fprintf(stderr, "json_to_u16 called!\n"); abort(); } +/* Generated stub for json_to_u32 */ +bool json_to_u32(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, + uint32_t *num UNNEEDED) +{ fprintf(stderr, "json_to_u32 called!\n"); abort(); } +/* Generated stub for json_to_u64 */ +bool json_to_u64(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, + uint64_t *num UNNEEDED) +{ fprintf(stderr, "json_to_u64 called!\n"); abort(); } +/* Generated stub for json_tok_bin_from_hex */ +u8 *json_tok_bin_from_hex(const tal_t *ctx UNNEEDED, const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED) +{ fprintf(stderr, "json_tok_bin_from_hex called!\n"); abort(); } +/* Generated stub for json_tok_full */ +const char *json_tok_full(const char *buffer UNNEEDED, const jsmntok_t *t UNNEEDED) +{ fprintf(stderr, "json_tok_full called!\n"); abort(); } +/* Generated stub for json_tok_full_len */ +int json_tok_full_len(const jsmntok_t *t UNNEEDED) +{ fprintf(stderr, "json_tok_full_len called!\n"); abort(); } +/* Generated stub for json_tok_streq */ +bool json_tok_streq(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, const char *str UNNEEDED) +{ fprintf(stderr, "json_tok_streq called!\n"); abort(); } +/* Generated stub for jsonrpc_request_start_ */ +struct out_req *jsonrpc_request_start_(struct plugin *plugin UNNEEDED, + struct command *cmd UNNEEDED, + const char *method UNNEEDED, + struct command_result *(*cb)(struct command *command UNNEEDED, + const char *buf UNNEEDED, + const jsmntok_t *result UNNEEDED, + void *arg) UNNEEDED, + struct command_result *(*errcb)(struct command *command UNNEEDED, + const char *buf UNNEEDED, + const jsmntok_t *result UNNEEDED, + void *arg) UNNEEDED, + void *arg UNNEEDED) +{ fprintf(stderr, "jsonrpc_request_start_ called!\n"); abort(); } +/* Generated stub for jsonrpc_stream_fail */ +struct json_stream *jsonrpc_stream_fail(struct command *cmd UNNEEDED, + int code UNNEEDED, + const char *err UNNEEDED) +{ fprintf(stderr, "jsonrpc_stream_fail called!\n"); abort(); } +/* Generated stub for jsonrpc_stream_success */ +struct json_stream *jsonrpc_stream_success(struct command *cmd UNNEEDED) +{ fprintf(stderr, "jsonrpc_stream_success called!\n"); abort(); } +/* Generated stub for notleak_ */ +void *notleak_(const void *ptr UNNEEDED, bool plus_children UNNEEDED) +{ fprintf(stderr, "notleak_ called!\n"); abort(); } +/* Generated stub for plugin_err */ +void plugin_err(struct plugin *p UNNEEDED, const char *fmt UNNEEDED, ...) +{ fprintf(stderr, "plugin_err called!\n"); abort(); } +/* Generated stub for plugin_log */ +void plugin_log(struct plugin *p UNNEEDED, enum log_level l UNNEEDED, const char *fmt UNNEEDED, ...) +{ fprintf(stderr, "plugin_log called!\n"); abort(); } +/* Generated stub for plugin_notification_end */ +void plugin_notification_end(struct plugin *plugin UNNEEDED, + struct json_stream *stream TAKES UNNEEDED) +{ fprintf(stderr, "plugin_notification_end called!\n"); abort(); } +/* Generated stub for plugin_notification_start */ +struct json_stream *plugin_notification_start(struct plugin *plugins UNNEEDED, + const char *method UNNEEDED) +{ fprintf(stderr, "plugin_notification_start called!\n"); abort(); } +/* Generated stub for random_select */ +bool random_select(double weight UNNEEDED, double *tot_weight UNNEEDED) +{ fprintf(stderr, "random_select called!\n"); abort(); } +/* Generated stub for send_outreq */ +struct command_result *send_outreq(struct plugin *plugin UNNEEDED, + const struct out_req *req UNNEEDED) +{ fprintf(stderr, "send_outreq 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(); } +/* AUTOGENERATED MOCKS END */ + +#ifndef SUPERVERBOSE +#define SUPERVERBOSE(...) +#endif + +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, + const struct short_channel_id *scid, + struct amount_msat min, + struct amount_msat max, + u32 base_fee, s32 proportional_fee, + u32 delay, + bool disable) +{ + secp256k1_ecdsa_signature dummy_sig; + u8 *msg; + + /* So valgrind doesn't complain */ + memset(&dummy_sig, 0, sizeof(dummy_sig)); + + 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, + min, + base_fee, + proportional_fee, + max); + + write_to_store(store_fd, msg); +} + +static void add_connection(int store_fd, + const struct node_id *from, + const struct node_id *to, + const struct short_channel_id *scid, + struct amount_msat min, + struct amount_msat max, + u32 base_fee, s32 proportional_fee, + u32 delay) +{ + 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(¬_a_secret, 1, sizeof(not_a_secret)); + pubkey_from_secret(¬_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); + + update_connection(store_fd, from, to, scid, min, max, + base_fee, proportional_fee, + delay, false); +} + +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 (ROUTING_MAX_HOPS + 1) + +/* We create an arrangement of nodes, each node N connected to N+1 and + * to node 1. The cost for each N to N+1 route is 1, for N to 1 is + * 2^N. That means it's always cheapest to go the longer route */ +int main(void) +{ + setup_locale(); + + struct node_id ids[NUM_NODES]; + int store_fd; + struct payment *p; + struct payment_modifier **mods; + struct gossmap *gossmap; + char gossip_version = GOSSIP_STORE_VERSION; + char gossipfilename[] = "/tmp/run-route-overlong.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); + + for (size_t i = 0; i < NUM_NODES; i++) { + struct privkey tmp; + memset(&tmp, i+1, sizeof(tmp)); + node_id_from_privkey(&tmp, &ids[i]); + } + + mods = tal_arrz(tmpctx, struct payment_modifier *, 1); + p = payment_new(mods, tal(tmpctx, struct command), NULL, mods); + + for (size_t i = 1; i < NUM_NODES; i++) { + struct short_channel_id scid; + + if (!mk_short_channel_id(&scid, i, i-1, 0)) + abort(); + add_connection(store_fd, &ids[i-1], &ids[i], &scid, + AMOUNT_MSAT(0), + AMOUNT_MSAT(1000000 * 1000), + 0, 0, 0); + SUPERVERBOSE("Joining %s to %s, fee %u\n", + type_to_string(tmpctx, struct node_id, &ids[i-1]), + type_to_string(tmpctx, struct node_id, &ids[i]), + 0); + + if (i <= 2) + continue; + if (!mk_short_channel_id(&scid, i, 1, 0)) + abort(); + add_connection(store_fd, &ids[1], &ids[i], &scid, + AMOUNT_MSAT(0), + AMOUNT_MSAT(1000000 * 1000), + 1 << i, 0, 0); + SUPERVERBOSE("Joining %s to %s, fee %u\n", + type_to_string(tmpctx, struct node_id, &ids[1]), + type_to_string(tmpctx, struct node_id, &ids[i]), + 1 << i); + } + + assert(gossmap_refresh(gossmap)); + for (size_t i = ROUTING_MAX_HOPS; i > 2; i--) { + struct gossmap_node *dst, *src; + struct route_hop *r; + const char *errmsg; + SUPERVERBOSE("%s -> %s:\n", + type_to_string(tmpctx, struct node_id, &ids[0]), + type_to_string(tmpctx, struct node_id, &ids[NUM_NODES-1])); + + src = gossmap_find_node(gossmap, &ids[0]); + dst = gossmap_find_node(gossmap, &ids[NUM_NODES-1]); + r = route(tmpctx, gossmap, src, dst, AMOUNT_MSAT(1000), 0, 0.0, + i - 1, p, &errmsg); + assert(r); + /* FIXME: We naively fall back on shortest, rather + * than biassing! */ + assert(tal_count(r) == 2); + } + + tal_free(tmpctx); + secp256k1_context_destroy(secp256k1_ctx); + return 0; +}