diff --git a/common/json_stream.c b/common/json_stream.c index 31a5367b15e4..81ff9ff6c33c 100644 --- a/common/json_stream.c +++ b/common/json_stream.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -494,6 +495,17 @@ void json_add_short_channel_id_dir(struct json_stream *response, short_channel_id_outnum(scidd.scid), scidd.dir); } +void json_add_route_exclusion(struct json_stream *response, + const char *fieldname, + const struct route_exclusion *ex) +{ + if (ex->type == EXCLUDE_NODE) + json_add_node_id(response, fieldname, &ex->u.node_id); + else + json_add_short_channel_id_dir(response, fieldname, + ex->u.chan_id); +} + static void json_add_address_fields(struct json_stream *response, const struct wireaddr *addr, const char *typefield) diff --git a/common/json_stream.h b/common/json_stream.h index eeca272c3e09..1962de0c4634 100644 --- a/common/json_stream.h +++ b/common/json_stream.h @@ -36,6 +36,7 @@ struct wally_psbt; struct lease_rates; struct wireaddr; struct wireaddr_internal; +struct route_exclusion; struct json_stream { struct json_out *jout; @@ -301,6 +302,12 @@ void json_add_channel_id(struct json_stream *response, const char *fieldname, const struct channel_id *cid); +/* '"fieldname" : ' or "" if fieldname is NULL, where is + * either a node_id or a short_channel_id_dir. */ +void json_add_route_exclusion(struct json_stream *response, + const char *fieldname, + const struct route_exclusion *ex); + /* '"fieldname" : ' or "" if fieldname is NULL */ void json_add_txid(struct json_stream *result, const char *fieldname, const struct bitcoin_txid *txid); diff --git a/contrib/msggen/msggen/schema.json b/contrib/msggen/msggen/schema.json index a9b6d1572ac4..f660f3ef43ed 100644 --- a/contrib/msggen/msggen/schema.json +++ b/contrib/msggen/msggen/schema.json @@ -361,6 +361,62 @@ "Main web site: " ] }, + "lightning-askrene-disable-channel.json": { + "$schema": "../rpc-schema-draft.json", + "type": "object", + "additionalProperties": false, + "rpc": "askrene-disable-channel", + "title": "Command to disable a channel in a layer (EXPERIMENTAL)", + "description": [ + "WARNING: experimental, so API may change.", + "", + "The **askrene-disable-channel** RPC command tells askrene to disable a channel whenever the given layer is used. This is mainly useful to force the use of alternate paths." + ], + "request": { + "required": [ + "layer", + "short_channel_id" + ], + "properties": { + "layer": { + "type": "string", + "description": [ + "The name of the layer to apply this change to." + ] + }, + "short_channel_id": { + "type": "short_channel_id", + "description": [ + "The channel to disable." + ] + }, + "direction": { + "type": "u32", + "description": [ + "The direction of the channel. If the direction is not specified then both directions are disabled." + ] + } + } + }, + "response": { + "required": [], + "properties": {} + }, + "see_also": [ + "lightning-getroutes(7)", + "lightning-askrene-create-channel(7)", + "lightning-askrene-inform-channel(7)", + "lightning-askrene-disable-node(7)", + "lightning-askrene-listlayers(7)", + "lightning-askrene-age(7)" + ], + "author": [ + "Rusty Russell <> is mainly responsible." + ], + "resources": [ + "Main web site: " + ] + }, "lightning-askrene-disable-node.json": { "$schema": "../rpc-schema-draft.json", "type": "object", @@ -555,7 +611,7 @@ "additionalProperties": false, "required": [ "layer", - "disabled_nodes", + "disabled", "created_channels", "constraints" ], @@ -566,12 +622,22 @@ "The name of the layer." ] }, - "disabled_nodes": { + "disabled": { "type": "array", "items": { - "type": "pubkey", - "description": [ - "The id of the disabled node." + "oneOf": [ + { + "type": "pubkey", + "description": [ + "The id of the disabled node." + ] + }, + { + "type": "short_channel_id_dir", + "description": [ + "The short channel id of the disabled channel." + ] + } ] } }, diff --git a/doc/Makefile b/doc/Makefile index fd59c4182646..719b0527f2f7 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -8,6 +8,7 @@ GENERATE_MARKDOWN := doc/lightning-addgossip.7 \ doc/lightning-addpsbtoutput.7 \ doc/lightning-askrene-create-channel.7 \ doc/lightning-askrene-disable-node.7 \ + doc/lightning-askrene-disable-channel.7 \ doc/lightning-askrene-inform-channel.7 \ doc/lightning-askrene-listlayers.7 \ doc/lightning-askrene-reserve.7 \ diff --git a/doc/index.rst b/doc/index.rst index 5515229db141..75e3d863f6d1 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -15,6 +15,7 @@ Core Lightning Documentation lightning-addgossip lightning-addpsbtoutput lightning-askrene-create-channel + lightning-askrene-disable-channel lightning-askrene-disable-node lightning-askrene-inform-channel lightning-askrene-listlayers diff --git a/doc/schemas/lightning-askrene-disable-channel.json b/doc/schemas/lightning-askrene-disable-channel.json new file mode 100644 index 000000000000..84f3f43042f0 --- /dev/null +++ b/doc/schemas/lightning-askrene-disable-channel.json @@ -0,0 +1,56 @@ +{ + "$schema": "../rpc-schema-draft.json", + "type": "object", + "additionalProperties": false, + "rpc": "askrene-disable-channel", + "title": "Command to disable a channel in a layer (EXPERIMENTAL)", + "description": [ + "WARNING: experimental, so API may change.", + "", + "The **askrene-disable-channel** RPC command tells askrene to disable a channel whenever the given layer is used. This is mainly useful to force the use of alternate paths." + ], + "request": { + "required": [ + "layer", + "short_channel_id" + ], + "properties": { + "layer": { + "type": "string", + "description": [ + "The name of the layer to apply this change to." + ] + }, + "short_channel_id": { + "type": "short_channel_id", + "description": [ + "The channel to disable." + ] + }, + "direction": { + "type": "u32", + "description": [ + "The direction of the channel. If the direction is not specified then both directions are disabled." + ] + } + } + }, + "response": { + "required": [], + "properties": {} + }, + "see_also": [ + "lightning-getroutes(7)", + "lightning-askrene-create-channel(7)", + "lightning-askrene-inform-channel(7)", + "lightning-askrene-disable-node(7)", + "lightning-askrene-listlayers(7)", + "lightning-askrene-age(7)" + ], + "author": [ + "Rusty Russell <> is mainly responsible." + ], + "resources": [ + "Main web site: " + ] +} diff --git a/doc/schemas/lightning-askrene-listlayers.json b/doc/schemas/lightning-askrene-listlayers.json index a0a5cba70511..9f996f8a54de 100644 --- a/doc/schemas/lightning-askrene-listlayers.json +++ b/doc/schemas/lightning-askrene-listlayers.json @@ -32,7 +32,7 @@ "additionalProperties": false, "required": [ "layer", - "disabled_nodes", + "disabled", "created_channels", "constraints" ], @@ -43,12 +43,22 @@ "The name of the layer." ] }, - "disabled_nodes": { + "disabled": { "type": "array", "items": { - "type": "pubkey", - "description": [ - "The id of the disabled node." + "oneOf": [ + { + "type": "pubkey", + "description": [ + "The id of the disabled node." + ] + }, + { + "type": "short_channel_id_dir", + "description": [ + "The short channel id of the disabled channel." + ] + } ] } }, diff --git a/plugins/askrene/askrene.c b/plugins/askrene/askrene.c index 751b687416ef..6aa47e90e5d8 100644 --- a/plugins/askrene/askrene.c +++ b/plugins/askrene/askrene.c @@ -337,6 +337,12 @@ static const char *get_routes(const tal_t *ctx, gossmap_apply_localmods(askrene->gossmap, localmods); + rq->disabled = tal_get_disabled_bitmap(rq, rq); + if (!rq->disabled) { + ret = tal_fmt(ctx, "Failed creation of disabled bitmap."); + goto out; + } + srcnode = gossmap_find_node(askrene->gossmap, source); if (!srcnode) { ret = tal_fmt(ctx, "Unknown source node %s", fmt_node_id(tmpctx, source)); @@ -881,6 +887,44 @@ static struct command_result *json_askrene_inform_channel(struct command *cmd, return command_finished(cmd, response); } +static struct command_result *json_askrene_disable_channel(struct command *cmd, + const char *buffer, + const jsmntok_t *params) +{ + struct short_channel_id *scid; + int *direction; + const char *layername; + struct layer *layer; + struct json_stream *response; + struct askrene *askrene = get_askrene(cmd->plugin); + + if (!param(cmd, buffer, params, + p_req("layer", param_layername, &layername), + p_req("short_channel_id", param_short_channel_id, &scid), + p_opt("direction", param_zero_or_one, &direction), + NULL)) + return command_param_failed(); + + layer = find_layer(askrene, layername); + if (!layer) + layer = new_layer(askrene, layername); + + struct short_channel_id_dir scidd = {.scid = *scid}; + if (direction) { + scidd.dir = *direction; + layer_add_disabled_channel(layer, &scidd); + } else { + /* If no direction is provided we disable both. */ + scidd.dir = 0; + layer_add_disabled_channel(layer, &scidd); + scidd.dir = 1; + layer_add_disabled_channel(layer, &scidd); + } + + response = jsonrpc_stream_success(cmd); + return command_finished(cmd, response); +} + static struct command_result *json_askrene_disable_node(struct command *cmd, const char *buffer, const jsmntok_t *params) @@ -983,6 +1027,10 @@ static const struct plugin_command commands[] = { "askrene-age", json_askrene_age, }, + { + "askrene-disable-channel", + json_askrene_disable_channel, + }, }; static void askrene_markmem(struct plugin *plugin, struct htable *memtable) diff --git a/plugins/askrene/askrene.h b/plugins/askrene/askrene.h index 7fb432838c8f..892d18204e80 100644 --- a/plugins/askrene/askrene.h +++ b/plugins/askrene/askrene.h @@ -2,6 +2,7 @@ #define LIGHTNING_PLUGINS_ASKRENE_ASKRENE_H #include "config.h" #include +#include #include #include #include @@ -41,6 +42,10 @@ struct route_query { /* This is *not* updated during a query! Has all layers applied. */ const struct gossmap *gossmap; + /* Bit is set for [idx*2+dir] if that channel is disabled or if it + * belongs to a disabled node.. */ + bitmap *disabled; + /* We need to take in-flight payments into account */ const struct reserve_htable *reserved; diff --git a/plugins/askrene/layer.c b/plugins/askrene/layer.c index 3991716dc988..ac37485e8ed2 100644 --- a/plugins/askrene/layer.c +++ b/plugins/askrene/layer.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -81,8 +82,8 @@ struct layer { /* Additional info, indexed by scid+dir */ struct constraint_hash *constraints; - /* Nodes to completely disable (tal_arr) */ - struct node_id *disabled_nodes; + /* Channels and nodes to disable (tal_arr). */ + struct route_exclusion *disabled; }; struct layer *new_temp_layer(const tal_t *ctx, const char *name) @@ -94,7 +95,7 @@ struct layer *new_temp_layer(const tal_t *ctx, const char *name) local_channel_hash_init(l->local_channels); l->constraints = tal(l, struct constraint_hash); constraint_hash_init(l->constraints); - l->disabled_nodes = tal_arr(l, struct node_id, 0); + l->disabled = tal_arr(l, struct route_exclusion, 0); return l; } @@ -299,7 +300,19 @@ size_t layer_trim_constraints(struct layer *layer, u64 cutoff) void layer_add_disabled_node(struct layer *layer, const struct node_id *node) { - tal_arr_expand(&layer->disabled_nodes, *node); + struct route_exclusion ex; + ex.type = EXCLUDE_NODE; + ex.u.node_id = *node; + tal_arr_expand(&layer->disabled, ex); +} + +void layer_add_disabled_channel(struct layer *layer, + const struct short_channel_id_dir *scidd) +{ + struct route_exclusion ex; + ex.type = EXCLUDE_CHANNEL; + ex.u.chan_id = *scidd; + tal_arr_expand(&layer->disabled, ex); } void layer_add_localmods(const struct layer *layer, @@ -310,34 +323,6 @@ void layer_add_localmods(const struct layer *layer, const struct local_channel *lc; struct local_channel_hash_iter lcit; - /* First, disable all channels into blocked nodes (local updates - * can add new ones)! */ - for (size_t i = 0; i < tal_count(layer->disabled_nodes); i++) { - const struct gossmap_node *node; - - node = gossmap_find_node(gossmap, &layer->disabled_nodes[i]); - if (!node) - continue; - for (size_t n = 0; n < node->num_chans; n++) { - struct short_channel_id scid; - struct gossmap_chan *c; - int dir; - c = gossmap_nth_chan(gossmap, node, n, &dir); - scid = gossmap_chan_scid(gossmap, c); - - /* Disabled zero-capacity on incoming */ - gossmap_local_updatechan(localmods, - scid, - AMOUNT_MSAT(0), - AMOUNT_MSAT(0), - 0, - 0, - 0, - false, - !dir); - } - } - for (lc = local_channel_hash_first(layer->local_channels, &lcit); lc; lc = local_channel_hash_next(layer->local_channels, &lcit)) { @@ -422,9 +407,9 @@ static void json_add_layer(struct json_stream *js, json_object_start(js, fieldname); json_add_string(js, "layer", layer->name); - json_array_start(js, "disabled_nodes"); - for (size_t i = 0; i < tal_count(layer->disabled_nodes); i++) - json_add_node_id(js, NULL, &layer->disabled_nodes[i]); + json_array_start(js, "disabled"); + for (size_t i = 0; i < tal_count(layer->disabled); i++) + json_add_route_exclusion(js, NULL, &layer->disabled[i]); json_array_end(js); json_array_start(js, "created_channels"); for (lc = local_channel_hash_first(layer->local_channels, &lcit); @@ -473,3 +458,56 @@ void layer_memleak_mark(struct askrene *askrene, struct htable *memtable) memleak_scan_htable(memtable, &l->local_channels->raw); } } + +static void set_channel_bit(bitmap *bm, const struct gossmap *gossmap, + const struct short_channel_id_dir *scidd) +{ + const struct gossmap_chan *chan = + gossmap_find_chan(gossmap, &scidd->scid); + + if (!chan) + return; + + bitmap_set_bit(bm, gossmap_chan_idx(gossmap, chan) * 2 + scidd->dir); +} + +static void set_node_channels_bit(bitmap *bm, const struct gossmap *gossmap, + const struct node_id *node_id) +{ + const struct gossmap_node *node = gossmap_find_node(gossmap, node_id); + for (size_t k = 0; k < node->num_chans; k++) { + int half; + const struct gossmap_chan *chan = + gossmap_nth_chan(gossmap, node, k, &half); + + bitmap_set_bit(bm, gossmap_chan_idx(gossmap, chan) * 2 + half); + bitmap_set_bit(bm, gossmap_chan_idx(gossmap, chan) * 2 + !half); + } +} + +bitmap *tal_get_disabled_bitmap(const tal_t *ctx, struct route_query *rq) +{ + + bitmap *disabled = tal_arrz( + ctx, bitmap, 2 * BITMAP_NWORDS(gossmap_max_chan_idx(rq->gossmap))); + + if (!disabled) + return NULL; + + /* Disable every channel in the list of disabled scids. */ + for (size_t i = 0; i < tal_count(rq->layers); i++) { + const struct layer *l = rq->layers[i]; + + for (size_t j = 0; j < tal_count(l->disabled); j++) { + const struct route_exclusion *ex = &l->disabled[j]; + + if (ex->type == EXCLUDE_CHANNEL) + set_channel_bit(disabled, rq->gossmap, + &ex->u.chan_id); + else + set_node_channels_bit(disabled, rq->gossmap, + &ex->u.node_id); + } + } + return disabled; +} diff --git a/plugins/askrene/layer.h b/plugins/askrene/layer.h index 7acea220c072..8ab655817090 100644 --- a/plugins/askrene/layer.h +++ b/plugins/askrene/layer.h @@ -102,6 +102,10 @@ size_t layer_trim_constraints(struct layer *layer, u64 cutoff); /* Add a disabled node to a layer. */ void layer_add_disabled_node(struct layer *layer, const struct node_id *node); +/* Add a disabled channel to a layer. */ +void layer_add_disabled_channel(struct layer *layer, + const struct short_channel_id_dir *scidd); + /* Print out a json object per layer, or all if layer is NULL */ void json_add_layers(struct json_stream *js, struct askrene *askrene, @@ -116,4 +120,9 @@ void json_add_constraint(struct json_stream *js, /* Scan for memleaks */ void layer_memleak_mark(struct askrene *askrene, struct htable *memtable); + +/* Creates a bitmap of disabled channels. It loops over all layers in the + * query searching for disabled channels and disabled nodes. All channels + * linked to a disabled node will be disabled. */ +bitmap *tal_get_disabled_bitmap(const tal_t *ctx, struct route_query *rq); #endif /* LIGHTNING_PLUGINS_ASKRENE_LAYER_H */ diff --git a/plugins/askrene/mcf.c b/plugins/askrene/mcf.c index 18b62ab7570e..ee85c3753ecf 100644 --- a/plugins/askrene/mcf.c +++ b/plugins/askrene/mcf.c @@ -583,11 +583,22 @@ static s64 linear_fee_cost( return pfee + bfee* base_fee_penalty+ delay*delay_feefactor; } +static bool channel_is_available(const struct gossmap_chan *c, int dir, + const struct gossmap *gossmap, + const bitmap *disabled) +{ + if (!gossmap_chan_set(c, dir)) + return false; + const u32 chan_idx = gossmap_chan_idx(gossmap, c); + return !bitmap_test_bit(disabled, chan_idx * 2 + dir); +} + static struct linear_network * init_linear_network(const tal_t *ctx, const struct pay_parameters *params) { struct linear_network * linear_network = tal(ctx, struct linear_network); const struct gossmap *gossmap = params->rq->gossmap; + const bitmap *disabled = params->rq->disabled; const size_t max_num_chans = gossmap_max_chan_idx(gossmap); const size_t max_num_arcs = max_num_chans * ARCS_PER_CHANNEL; @@ -630,7 +641,7 @@ init_linear_network(const tal_t *ctx, const struct pay_parameters *params) const struct gossmap_chan *c = gossmap_nth_chan(gossmap, node, j, &half); - if (!gossmap_chan_set(c, half)) + if (!channel_is_available(c, half, gossmap, disabled)) continue; const u32 chan_id = gossmap_chan_idx(gossmap, c); diff --git a/plugins/renepay/Makefile b/plugins/renepay/Makefile index 501db8a51c57..aa3163eca6b2 100644 --- a/plugins/renepay/Makefile +++ b/plugins/renepay/Makefile @@ -1,37 +1,21 @@ PLUGIN_RENEPAY_SRC := \ plugins/renepay/main.c \ - plugins/renepay/flow.c \ - plugins/renepay/mcf.c \ - plugins/renepay/dijkstra.c \ - plugins/renepay/disabledmap.c \ plugins/renepay/payment.c \ - plugins/renepay/chan_extra.c \ plugins/renepay/route.c \ - plugins/renepay/routebuilder.c \ plugins/renepay/routetracker.c \ plugins/renepay/routefail.c \ - plugins/renepay/uncertainty.c \ plugins/renepay/mods.c \ - plugins/renepay/errorcodes.c \ plugins/renepay/json.c PLUGIN_RENEPAY_HDRS := \ plugins/renepay/payplugin.h \ - plugins/renepay/flow.h \ - plugins/renepay/mcf.h \ - plugins/renepay/dijkstra.h \ - plugins/renepay/disabledmap.h \ plugins/renepay/payment.h \ plugins/renepay/payment_info.h \ - plugins/renepay/chan_extra.h \ plugins/renepay/renepayconfig.h \ plugins/renepay/route.h \ - plugins/renepay/routebuilder.h \ plugins/renepay/routetracker.h \ plugins/renepay/routefail.h \ - plugins/renepay/uncertainty.h \ plugins/renepay/mods.h \ - plugins/renepay/errorcodes.h \ plugins/renepay/json.c PLUGIN_RENEPAY_OBJS := $(PLUGIN_RENEPAY_SRC:.c=.o) diff --git a/plugins/renepay/chan_extra.c b/plugins/renepay/chan_extra.c deleted file mode 100644 index 57183227ea3b..000000000000 --- a/plugins/renepay/chan_extra.c +++ /dev/null @@ -1,682 +0,0 @@ -#include "config.h" -#include -#include -#include -#include -#include -#include - -bool chan_extra_is_busy(const struct chan_extra *const ce) -{ - if (ce == NULL) - return false; - return ce->half[0].num_htlcs || ce->half[1].num_htlcs; -} - -const char *fmt_chan_extra_map(const tal_t *ctx, - struct chan_extra_map *chan_extra_map) -{ - tal_t *this_ctx = tal(ctx, tal_t); - char *buff = tal_fmt(ctx, "Uncertainty network:\n"); - struct chan_extra_map_iter it; - for (struct chan_extra *ch = chan_extra_map_first(chan_extra_map, &it); - ch; ch = chan_extra_map_next(chan_extra_map, &it)) { - const char *scid_str = fmt_short_channel_id(this_ctx, ch->scid); - for (int dir = 0; dir < 2; ++dir) { - tal_append_fmt( - &buff, "%s[%d]:(%s,%s) htlc: %s\n", scid_str, dir, - fmt_amount_msat(this_ctx, ch->half[dir].known_min), - fmt_amount_msat(this_ctx, ch->half[dir].known_max), - fmt_amount_msat(this_ctx, ch->half[dir].htlc_total)); - } - } - tal_free(this_ctx); - return buff; -} - -const char *fmt_chan_extra_details(const tal_t *ctx, - const struct chan_extra_map *chan_extra_map, - const struct short_channel_id_dir *scidd) -{ - const tal_t *this_ctx = tal(ctx, tal_t); - const struct chan_extra *ce = - chan_extra_map_get(chan_extra_map, scidd->scid); - const struct chan_extra_half *ch; - char *str = tal_strdup(ctx, ""); - char sep = '('; - - if (!ce) { - // we have no information on this channel - tal_append_fmt(&str, "()"); - goto finished; - } - - ch = &ce->half[scidd->dir]; - if (ch->num_htlcs != 0) { - tal_append_fmt(&str, "%c%s in %zu htlcs", sep, - fmt_amount_msat(this_ctx, ch->htlc_total), - ch->num_htlcs); - sep = ','; - } - /* Happens with local channels, where we're certain. */ - if (amount_msat_eq(ch->known_min, ch->known_max)) { - tal_append_fmt(&str, "%cmin=max=%s", sep, - fmt_amount_msat(this_ctx, ch->known_min)); - sep = ','; - } else { - if (amount_msat_greater(ch->known_min, AMOUNT_MSAT(0))) { - tal_append_fmt( - &str, "%cmin=%s", sep, - fmt_amount_msat(this_ctx, ch->known_min)); - sep = ','; - } - if (!amount_msat_eq(ch->known_max, ce->capacity)) { - tal_append_fmt( - &str, "%cmax=%s", sep, - fmt_amount_msat(this_ctx, ch->known_max)); - sep = ','; - } - } - if (!streq(str, "")) - tal_append_fmt(&str, ")"); - -finished: - tal_free(this_ctx); - return str; -} - -struct chan_extra *new_chan_extra(struct chan_extra_map *chan_extra_map, - const struct short_channel_id scid, - struct amount_msat capacity) -{ - assert(chan_extra_map); - struct chan_extra *ce = tal(chan_extra_map, struct chan_extra); - if (!ce) - return ce; - - ce->scid = scid; - ce->capacity = capacity; - for (size_t i = 0; i <= 1; i++) { - ce->half[i].num_htlcs = 0; - ce->half[i].htlc_total = AMOUNT_MSAT(0); - ce->half[i].known_min = AMOUNT_MSAT(0); - ce->half[i].known_max = capacity; - } - if (!chan_extra_map_add(chan_extra_map, ce)) { - return tal_free(ce); - } - - /* Remove self from map when done */ - // TODO(eduardo): - // Is this desctructor really necessary? the chan_extra will deallocated - // when the chan_extra_map is freed. Anyways valgrind complains that the - // hash table is removing the element with a freed pointer. - // tal_add_destructor2(ce, destroy_chan_extra, chan_extra_map); - return ce; -} - -/* Based on the knowledge that we have and HTLCs, returns the greatest - * amount that we can send through this channel. */ -enum renepay_errorcode channel_liquidity(struct amount_msat *liquidity, - const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map, - const struct gossmap_chan *chan, - const int dir) -{ - const struct chan_extra_half *h = - get_chan_extra_half_by_chan(gossmap, chan_extra_map, chan, dir); - if (!h) - return RENEPAY_CHANNEL_NOT_FOUND; - struct amount_msat value_liquidity = h->known_max; - if (!amount_msat_sub(&value_liquidity, value_liquidity, h->htlc_total)) - return RENEPAY_AMOUNT_OVERFLOW; - *liquidity = value_liquidity; - return RENEPAY_NOERROR; -} - -/* Checks BOLT 7 HTLC fee condition: - * recv >= base_fee + (send*proportional_fee)/1000000 */ -bool check_fee_inequality(struct amount_msat recv, struct amount_msat send, - u64 base_fee, u64 proportional_fee) -{ - // nothing to forward, any incoming amount is good - if (amount_msat_is_zero(send)) - return true; - // FIXME If this addition fails we return false. The caller will not be - // able to know that there was an addition overflow, he will just assume - // that the fee inequality was not satisfied. - if (!amount_msat_add_fee(&send, base_fee, proportional_fee)) - return false; - return amount_msat_greater_eq(recv, send); -} - -/* Let `recv` be the maximum amount this channel can receive, this function - * computes the maximum amount this channel can forward `send`. - * From BOLT7 specification wee need to satisfy the following inequality: - * - * recv-send >= base_fee + floor(send*proportional_fee/1000000) - * - * That is equivalent to have - * - * send <= Bound(recv,send) - * - * where - * - * Bound(recv, send) = ((recv - base_fee)*1000000 + (send*proportional_fee) - *% 1000000)/(proportional_fee+1000000) - * - * However the quantity we want to determine, `send`, appears on both sides of - * the equation. However the term `send*proportional_fee) % 1000000` only - * contributes by increasing the bound by at most one so that we can neglect - * the extra term and use instead - * - * Bound_simple(recv) = ((recv - - *base_fee)*1000000)/(proportional_fee+1000000) - * - * as the upper bound for `send`. Formally one can check that - * - * Bound_simple(recv) <= Bound(recv, send) < Bound_simple(recv) + 2 - * - * So that if one wishes to find the very highest value of `send` that - * satisfies - * - * send <= Bound(recv, send) - * - * it is enough to compute - * - * send = Bound_simple(recv) - * - * which already satisfies the fee equation and then try to go higher - * with send+1, send+2, etc. But we know that it is enough to try up to - * send+1 because Bound(recv, send) < Bound_simple(recv) + 2. - * */ -enum renepay_errorcode channel_maximum_forward(struct amount_msat *max_forward, - const struct gossmap_chan *chan, - const int dir, - struct amount_msat recv) -{ - const u64 b = chan->half[dir].base_fee, - p = chan->half[dir].proportional_fee; - - const u64 one_million = 1000000; - u64 x_msat = - recv.millisatoshis; /* Raw: need to invert the fee equation */ - - // special case, when recv - base_fee <= 0, we cannot forward anything - if (x_msat <= b) { - *max_forward = amount_msat(0); - return RENEPAY_NOERROR; - } - - x_msat -= b; - - if (mul_overflows_u64(one_million, x_msat)) - return RENEPAY_AMOUNT_OVERFLOW; - - struct amount_msat best_send = - AMOUNT_MSAT_INIT((one_million * x_msat) / (one_million + p)); - - /* Try to increase the value we send (up tp the last millisat) until we - * fail to fulfill the fee inequality. It takes only one iteration - * though. */ - for (size_t i = 0; i < 10; ++i) { - struct amount_msat next_send; - if (!amount_msat_add(&next_send, best_send, amount_msat(1))) - return RENEPAY_AMOUNT_OVERFLOW; - - if (check_fee_inequality(recv, next_send, b, p)) - best_send = next_send; - else - break; - } - *max_forward = best_send; - return RENEPAY_NOERROR; -} - -/* This helper function preserves the uncertainty network invariant after the - * knowledge is updated. It assumes that the (channel,!dir) knowledge is - * correct. */ -static enum renepay_errorcode chan_extra_adjust_half(struct chan_extra *ce, - int dir) -{ - assert(ce); - assert(dir == 0 || dir == 1); - - struct amount_msat new_known_max, new_known_min; - - if (!amount_msat_sub(&new_known_max, ce->capacity, - ce->half[!dir].known_min) || - !amount_msat_sub(&new_known_min, ce->capacity, - ce->half[!dir].known_max)) - return RENEPAY_AMOUNT_OVERFLOW; - - ce->half[dir].known_max = new_known_max; - ce->half[dir].known_min = new_known_min; - return RENEPAY_NOERROR; -} - -/* Update the knowledge that this (channel,direction) can send x msat.*/ -static enum renepay_errorcode -chan_extra_can_send_(struct chan_extra *ce, int dir, struct amount_msat x) -{ - assert(ce); - assert(dir == 0 || dir == 1); - enum renepay_errorcode err; - - if (amount_msat_greater(x, ce->capacity)) - return RENEPAY_PRECONDITION_ERROR; - - struct amount_msat known_min, known_max; - - // in case we fail, let's remember the original state - known_min = ce->half[dir].known_min; - known_max = ce->half[dir].known_max; - - ce->half[dir].known_min = amount_msat_max(ce->half[dir].known_min, x); - ce->half[dir].known_max = amount_msat_max(ce->half[dir].known_max, x); - - err = chan_extra_adjust_half(ce, !dir); - if (err != RENEPAY_NOERROR) - goto restore_and_fail; - - return RENEPAY_NOERROR; - -restore_and_fail: - // we fail, thus restore the original state - ce->half[dir].known_min = known_min; - ce->half[dir].known_max = known_max; - return err; -} - -enum renepay_errorcode -chan_extra_can_send(struct chan_extra_map *chan_extra_map, - const struct short_channel_id_dir *scidd) -{ - assert(scidd); - assert(chan_extra_map); - struct chan_extra *ce = chan_extra_map_get(chan_extra_map, scidd->scid); - if (!ce) - return RENEPAY_CHANNEL_NOT_FOUND; - return chan_extra_can_send_(ce, scidd->dir, - ce->half[scidd->dir].htlc_total); -} - -/* Update the knowledge that this (channel,direction) cannot send.*/ -enum renepay_errorcode -chan_extra_cannot_send(struct chan_extra_map *chan_extra_map, - const struct short_channel_id_dir *scidd) -{ - assert(scidd); - assert(chan_extra_map); - struct amount_msat x; - enum renepay_errorcode err; - struct chan_extra *ce = chan_extra_map_get(chan_extra_map, scidd->scid); - if (!ce) - return RENEPAY_CHANNEL_NOT_FOUND; - - /* Note: sent is already included in htlc_total! */ - if (!amount_msat_sub(&x, ce->half[scidd->dir].htlc_total, - AMOUNT_MSAT(1))) - return RENEPAY_AMOUNT_OVERFLOW; - - struct amount_msat known_min, known_max; - // in case we fail, let's remember the original state - known_min = ce->half[scidd->dir].known_min; - known_max = ce->half[scidd->dir].known_max; - - /* If we "knew" the capacity was at least this, we just showed we're - * wrong! */ - if (amount_msat_less(x, ce->half[scidd->dir].known_min)) { - /* Skip to half of x, since we don't know (rounds down) */ - ce->half[scidd->dir].known_min = amount_msat_div(x, 2); - } - - ce->half[scidd->dir].known_max = - amount_msat_min(ce->half[scidd->dir].known_max, x); - - err = chan_extra_adjust_half(ce, !scidd->dir); - if (err != RENEPAY_NOERROR) - goto restore_and_fail; - return err; - -restore_and_fail: - // we fail, thus restore the original state - ce->half[scidd->dir].known_min = known_min; - ce->half[scidd->dir].known_max = known_max; - return err; -} - -/* Update the knowledge that this (channel,direction) has liquidity x.*/ -// FIXME for being this low level API, I thinkg it's too much to have verbose -// error messages -static enum renepay_errorcode chan_extra_set_liquidity_(struct chan_extra *ce, - int dir, - struct amount_msat min, - struct amount_msat max) -{ - assert(ce); - assert(dir == 0 || dir == 1); - enum renepay_errorcode err; - - if (amount_msat_greater(max, ce->capacity) || - amount_msat_greater(min, max)) - return RENEPAY_PRECONDITION_ERROR; - - // in case we fail, let's remember the original state - struct amount_msat known_min, known_max; - known_min = ce->half[dir].known_min; - known_max = ce->half[dir].known_max; - - ce->half[dir].known_min = min; - ce->half[dir].known_max = max; - - err = chan_extra_adjust_half(ce, !dir); - if (err != RENEPAY_NOERROR) - goto restore_and_fail; - return err; - -restore_and_fail: - // we fail, thus restore the original state - ce->half[dir].known_min = known_min; - ce->half[dir].known_max = known_max; - return err; -} - -enum renepay_errorcode -chan_extra_set_liquidity(struct chan_extra_map *chan_extra_map, - const struct short_channel_id_dir *scidd, - struct amount_msat min, - struct amount_msat max) -{ - assert(scidd); - assert(chan_extra_map); - struct chan_extra *ce = chan_extra_map_get(chan_extra_map, scidd->scid); - if (!ce) - return RENEPAY_CHANNEL_NOT_FOUND; - - return chan_extra_set_liquidity_(ce, scidd->dir, min, max); -} - -/* Update the knowledge that this (channel,direction) has sent x msat.*/ -enum renepay_errorcode -chan_extra_sent_success(struct chan_extra_map *chan_extra_map, - const struct short_channel_id_dir *scidd, - struct amount_msat x) -{ - assert(scidd); - assert(chan_extra_map); - - struct chan_extra *ce = chan_extra_map_get(chan_extra_map, scidd->scid); - if (!ce) - return RENEPAY_CHANNEL_NOT_FOUND; - - // if we sent amount x, it first means that all htlcs on this channel - // fit in the liquidity - enum renepay_errorcode err; - err = chan_extra_can_send(chan_extra_map, scidd); - if (err != RENEPAY_NOERROR) - return err; - - if (amount_msat_greater(x, ce->capacity)) - return RENEPAY_PRECONDITION_ERROR; - - // in case we fail, let's remember the original state - struct amount_msat known_min, known_max; - known_min = ce->half[scidd->dir].known_min; - known_max = ce->half[scidd->dir].known_max; - - struct amount_msat new_a, new_b; - - if (!amount_msat_sub(&new_a, ce->half[scidd->dir].known_min, x)) - new_a = AMOUNT_MSAT(0); - if (!amount_msat_sub(&new_b, ce->half[scidd->dir].known_max, x)) - new_b = AMOUNT_MSAT(0); - - ce->half[scidd->dir].known_min = new_a; - ce->half[scidd->dir].known_max = new_b; - - err = chan_extra_adjust_half(ce, !scidd->dir); - if (err != RENEPAY_NOERROR) - goto restore_and_fail; - - return err; - -// we fail, thus restore the original state -restore_and_fail: - ce->half[scidd->dir].known_min = known_min; - ce->half[scidd->dir].known_max = known_max; - return err; -} - -/* Forget a bit about this (channel,direction) state. */ -static enum renepay_errorcode chan_extra_relax(struct chan_extra *ce, int dir, - struct amount_msat down, - struct amount_msat up) -{ - assert(ce); - assert(dir == 0 || dir == 1); - struct amount_msat new_a, new_b; - enum renepay_errorcode err; - - if (!amount_msat_sub(&new_a, ce->half[dir].known_min, down)) - new_a = AMOUNT_MSAT(0); - if (!amount_msat_add(&new_b, ce->half[dir].known_max, up)) - new_b = ce->capacity; - new_b = amount_msat_min(new_b, ce->capacity); - - // in case we fail, let's remember the original state - struct amount_msat known_min, known_max; - known_min = ce->half[dir].known_min; - known_max = ce->half[dir].known_max; - - ce->half[dir].known_min = new_a; - ce->half[dir].known_max = new_b; - - err = chan_extra_adjust_half(ce, !dir); - if (err != RENEPAY_NOERROR) - goto restore_and_fail; - return err; - -// we fail, thus restore the original state -restore_and_fail: - ce->half[dir].known_min = known_min; - ce->half[dir].known_max = known_max; - return err; -} - -/* Forget the channel information by a fraction of the capacity. */ -enum renepay_errorcode chan_extra_relax_fraction(struct chan_extra *ce, - double fraction) -{ - assert(ce); - assert(fraction >= 0); - /* Allow to have values greater than 1 to indicate full relax. */ - // assert(fraction<=1); - fraction = fabs(fraction); // this number is always non-negative - fraction = MIN(1.0, fraction); // this number cannot be greater than 1. - struct amount_msat delta = - amount_msat(ce->capacity.millisatoshis*fraction); /* Raw: get a fraction of the capacity */ - - /* The direction here is not important because the 'down' and the 'up' - * limits are changed by the same amount. - * Notice that if chan[0] with capacity C changes from (a,b) to - * (a-d,b+d) then its counterpart chan[1] changes from (C-b,C-a) to - * (C-b-d,C-a+d), hence both dirs are applied the same transformation. - */ - return chan_extra_relax(ce, /*dir=*/0, delta, delta); -} - -/* Returns either NULL, or an entry from the hash */ -struct chan_extra_half * -get_chan_extra_half_by_scid(struct chan_extra_map *chan_extra_map, - const struct short_channel_id_dir *scidd) -{ - assert(scidd); - assert(chan_extra_map); - struct chan_extra *ce; - - ce = chan_extra_map_get(chan_extra_map, scidd->scid); - if (!ce) - return NULL; - return &ce->half[scidd->dir]; -} -/* Helper if we have a gossmap_chan */ -struct chan_extra_half * -get_chan_extra_half_by_chan(const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map, - const struct gossmap_chan *chan, int dir) -{ - assert(chan); - assert(dir == 0 || dir == 1); - assert(gossmap); - assert(chan_extra_map); - struct short_channel_id_dir scidd; - - scidd.scid = gossmap_chan_scid(gossmap, chan); - scidd.dir = dir; - return get_chan_extra_half_by_scid(chan_extra_map, &scidd); -} - -// static void destroy_chan_extra(struct chan_extra *ce, -// struct chan_extra_map *chan_extra_map) -// { -// chan_extra_map_del(chan_extra_map, ce); -// } -/* Helper to get the chan_extra_half. If it doesn't exist create a new one. */ -struct chan_extra_half * -get_chan_extra_half_by_chan_verify(const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map, - const struct gossmap_chan *chan, int dir) -{ - assert(chan); - assert(dir == 0 || dir == 1); - assert(gossmap); - assert(chan_extra_map); - struct short_channel_id_dir scidd; - - scidd.scid = gossmap_chan_scid(gossmap, chan); - scidd.dir = dir; - struct chan_extra_half *h = - get_chan_extra_half_by_scid(chan_extra_map, &scidd); - if (!h) { - struct amount_sat cap; - struct amount_msat cap_msat; - - if (!gossmap_chan_get_capacity(gossmap, chan, &cap) || - !amount_sat_to_msat(&cap_msat, cap)) { - return NULL; - } - h = &new_chan_extra(chan_extra_map, scidd.scid, cap_msat) - ->half[scidd.dir]; - } - return h; -} - -/* Assuming a uniform distribution, what is the chance this f gets through? - * Here we compute the conditional probability of success for a flow f, given - * the knowledge that the liquidity is in the range [a,b) and some amount - * x is already committed on another part of the payment. - * - * The probability equation for x=0 is: - * - * prob(f) = - * - * for f=f>=a: (b-f)/(b-a) - * for b0 the prob. of success for passing x and f is: - * - * prob(f and x) = prob(x) * prob(f|x) - * - * and it can be shown to be equal to - * - * prob(f and x) = prob(f+x) - * - * The purpose of this function is to obtain prob(f|x), i.e. the probability of - * getting f through provided that we already succeeded in getting x. - * This conditional probability comes with 4 cases: - * - * prob(f|x) = - * - * for x=a-x: (b-x-f)/(b-a) - * for x>=a: (b-x-f)/(b-x) - * for f>b-x: 0. - * - * This is the same as the probability of success of f when the bounds are - * shifted by x amount, the new bounds be [MAX(0,a-x),b-x). - */ -double edge_probability(struct amount_msat min, struct amount_msat max, - struct amount_msat in_flight, struct amount_msat f) -{ - assert(amount_msat_less_eq(min, max)); - assert(amount_msat_less_eq(in_flight, max)); - - const struct amount_msat one = AMOUNT_MSAT(1); - struct amount_msat B = max; // = max +1 - in_flight - - // one past the last known value, makes computations simpler - if (!amount_msat_accumulate(&B, one)) - goto function_fail; - - // in_flight cannot be greater than max - if (!amount_msat_sub(&B, B, in_flight)) - goto function_fail; - - struct amount_msat A = min; // = MAX(0,min-in_flight); - - if (!amount_msat_sub(&A, A, in_flight)) - A = AMOUNT_MSAT(0); - - struct amount_msat denominator; // = B-A - - // B cannot be smaller than or equal A - if (!amount_msat_sub(&denominator, B, A) || amount_msat_less_eq(B, A)) - goto function_fail; - - struct amount_msat numerator; // MAX(0,B-f) - - if (!amount_msat_sub(&numerator, B, f)) - numerator = AMOUNT_MSAT(0); - - return amount_msat_less_eq(f, A) - ? 1.0 - : amount_msat_ratio(numerator, denominator); - -function_fail: - return -1; -} - -enum renepay_errorcode -chan_extra_remove_htlc(struct chan_extra_map *chan_extra_map, - const struct short_channel_id_dir *scidd, - struct amount_msat amount) -{ - struct chan_extra_half *h = - get_chan_extra_half_by_scid(chan_extra_map, scidd); - if (!h) - return RENEPAY_CHANNEL_NOT_FOUND; - if (h->num_htlcs <= 0) - return RENEPAY_PRECONDITION_ERROR; - - if (!amount_msat_sub(&h->htlc_total, h->htlc_total, amount)) - return RENEPAY_AMOUNT_OVERFLOW; - h->num_htlcs--; - return RENEPAY_NOERROR; -} - -enum renepay_errorcode -chan_extra_commit_htlc(struct chan_extra_map *chan_extra_map, - const struct short_channel_id_dir *scidd, - struct amount_msat amount) -{ - struct chan_extra_half *h = - get_chan_extra_half_by_scid(chan_extra_map, scidd); - if (!h) - return RENEPAY_CHANNEL_NOT_FOUND; - if (!amount_msat_accumulate(&h->htlc_total, amount)) - return RENEPAY_AMOUNT_OVERFLOW; - h->num_htlcs++; - return RENEPAY_NOERROR; -} diff --git a/plugins/renepay/chan_extra.h b/plugins/renepay/chan_extra.h deleted file mode 100644 index cf06d166a8fd..000000000000 --- a/plugins/renepay/chan_extra.h +++ /dev/null @@ -1,209 +0,0 @@ -#ifndef LIGHTNING_PLUGINS_RENEPAY_CHAN_EXTRA_H -#define LIGHTNING_PLUGINS_RENEPAY_CHAN_EXTRA_H - -#include "config.h" -#include -#include -#include -#include -#include - -#define MAX(x, y) (((x) > (y)) ? (x) : (y)) -#define MIN(x, y) (((x) < (y)) ? (x) : (y)) - -/* Any implementation needs to keep some data on channels which are - * in-use (or about which we have extra information). We use a hash - * table here, since most channels are not in use. */ -// TODO(eduardo): if we know the liquidity of channel (X,dir) is [A,B] -// then we also know that the liquidity of channel (X,!dir) is [Cap-B,Cap-A]. -// This means that it is redundant to store known_min and known_max for both -// halves of the channel and it also means that once we update the knowledge of -// (X,dir) the knowledge of (X,!dir) is updated as well. -struct chan_extra { - struct short_channel_id scid; - struct amount_msat capacity; - - struct chan_extra_half { - /* How many htlcs we've directed through it */ - size_t num_htlcs; - - /* The total size of those HTLCs */ - struct amount_msat htlc_total; - - /* The known minimum / maximum capacity (if nothing known, - * 0/capacity */ - struct amount_msat known_min, known_max; - } half[2]; -}; - -bool chan_extra_is_busy(const struct chan_extra *const ce); - -static inline const struct short_channel_id -chan_extra_scid(const struct chan_extra *cd) -{ - return cd->scid; -} - -static inline bool chan_extra_eq_scid(const struct chan_extra *cd, - const struct short_channel_id scid) -{ - return short_channel_id_eq(scid, cd->scid); -} - -HTABLE_DEFINE_TYPE(struct chan_extra, chan_extra_scid, short_channel_id_hash, - chan_extra_eq_scid, chan_extra_map); - -/* Helpers for chan_extra_map */ -/* Channel knowledge invariants: - * - * 0<=a<=b<=capacity - * - * a_inv = capacity-b - * b_inv = capacity-a - * - * where a,b are the known minimum and maximum liquidities, and a_inv and b_inv - * are the known minimum and maximum liquidities for the channel in the opposite - * direction. - * - * Knowledge update operations can be: - * - * 1. set liquidity (x) - * (a,b) -> (x,x) - * - * The entropy is minimum here (=0). - * - * 2. can send (x): - * xb = min(x,capacity) - * (a,b) -> (max(a,xb),max(b,xb)) - * - * If x<=a then there is no new knowledge and the entropy remains - * the same. - * If x>a the entropy decreases. - * - * - * 3. can't send (x): - * xb = max(0,x-1) - * (a,b) -> (min(a,xb),min(b,xb)) - * - * If x>b there is no new knowledge and the entropy remains. - * If x<=b then the entropy decreases. - * - * 4. sent success (x): - * (a,b) -> (max(0,a-x),max(0,b-x)) - * - * If x<=a there is no new knowledge and the entropy remains. - * If a (max(0,a-x),min(capacity,b+y)) - * - * Entropy increases unless it is already maximum. - * */ - -const char *fmt_chan_extra_map(const tal_t *ctx, - struct chan_extra_map *chan_extra_map); - -/* Returns "" if nothing useful known about channel, otherwise - * "(details)" */ -const char *fmt_chan_extra_details(const tal_t *ctx, - const struct chan_extra_map *chan_extra_map, - const struct short_channel_id_dir *scidd); - -/* Creates a new chan_extra and adds it to the chan_extra_map. */ -struct chan_extra *new_chan_extra(struct chan_extra_map *chan_extra_map, - const struct short_channel_id scid, - struct amount_msat capacity); - -/* Update the knowledge that this (channel,direction) can send x msat.*/ -enum renepay_errorcode -chan_extra_can_send(struct chan_extra_map *chan_extra_map, - const struct short_channel_id_dir *scidd); - -/* Update the knowledge that this (channel,direction) cannot send x msat.*/ -enum renepay_errorcode -chan_extra_cannot_send(struct chan_extra_map *chan_extra_map, - const struct short_channel_id_dir *scidd); - -enum renepay_errorcode -chan_extra_remove_htlc(struct chan_extra_map *chan_extra_map, - const struct short_channel_id_dir *scidd, - struct amount_msat amount); - -enum renepay_errorcode -chan_extra_commit_htlc(struct chan_extra_map *chan_extra_map, - const struct short_channel_id_dir *scidd, - struct amount_msat amount); - - -/* Update the knowledge that this (channel,direction) has liquidity x.*/ -enum renepay_errorcode -chan_extra_set_liquidity(struct chan_extra_map *chan_extra_map, - const struct short_channel_id_dir *scidd, - struct amount_msat min, - struct amount_msat max); - -/* Update the knowledge that this (channel,direction) has sent x msat.*/ -enum renepay_errorcode -chan_extra_sent_success(struct chan_extra_map *chan_extra_map, - const struct short_channel_id_dir *scidd, - struct amount_msat x); - -/* Forget the channel information by a fraction of the capacity. */ -enum renepay_errorcode chan_extra_relax_fraction(struct chan_extra *ce, - double fraction); - -/* Returns either NULL, or an entry from the hash */ -struct chan_extra_half * -get_chan_extra_half_by_scid(struct chan_extra_map *chan_extra_map, - const struct short_channel_id_dir *scidd); -/* If the channel is not registered, then a new entry is created. scid must be - * present in the gossmap. */ -struct chan_extra_half * -get_chan_extra_half_by_chan_verify(const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map, - const struct gossmap_chan *chan, int dir); - -/* Helper if we have a gossmap_chan */ -struct chan_extra_half * -get_chan_extra_half_by_chan(const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map, - const struct gossmap_chan *chan, int dir); - -/* Based on the knowledge that we have and HTLCs, returns the greatest - * amount that we can send through this channel. */ -enum renepay_errorcode channel_liquidity(struct amount_msat *liquidity, - const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map, - const struct gossmap_chan *chan, - const int dir); - -/* inputs - * @chan: a channel - * @recv: how much can we send to this channels - * - * output - * @max_forward: how much can we ask this channel to forward to the next hop - * */ -enum renepay_errorcode channel_maximum_forward(struct amount_msat *max_forward, - const struct gossmap_chan *chan, - const int dir, - struct amount_msat recv); - -/* Assume a uniform distribution: - * @min, @max: the bounds of liquidity - * @in_flight: htlcs - * - * @f: the amount we want to forward - * - * returns the probability that this forward request gets through. - * */ -double edge_probability(struct amount_msat min, struct amount_msat max, - struct amount_msat in_flight, struct amount_msat f); - -/* Checks BOLT 7 HTLC fee condition: - * recv >= base_fee + (send*proportional_fee)/1000000 */ -bool check_fee_inequality(struct amount_msat recv, struct amount_msat send, - u64 base_fee, u64 proportional_fee); - -#endif /* LIGHTNING_PLUGINS_RENEPAY_CHAN_EXTRA_H */ diff --git a/plugins/renepay/dijkstra.c b/plugins/renepay/dijkstra.c deleted file mode 100644 index db41ebaf902d..000000000000 --- a/plugins/renepay/dijkstra.c +++ /dev/null @@ -1,186 +0,0 @@ -#define NDEBUG 1 -#include "config.h" -#include - -/* In the heap we keep node idx, but in this structure we keep the distance - * value associated to every node, and their position in the heap as a pointer - * so that we can update the nodes inside the heap when the distance label is - * changed. - * - * Therefore this is no longer a multipurpose heap, the node_idx must be an - * index between 0 and less than max_num_nodes. */ -struct dijkstra { - // - s64 *distance; - u32 *base; - u32 **heapptr; - size_t heapsize; - struct gheap_ctx gheap_ctx; -}; - -static const s64 INFINITE = INT64_MAX; - -/* Required a global dijkstra for gheap. */ -static struct dijkstra *global_dijkstra; - -/* The heap comparer for Dijkstra search. Since the top element must be the one - * with the smallest distance, we use the operator >, rather than <. */ -static int dijkstra_less_comparer( - const void *const ctx UNUSED, - const void *const a, - const void *const b) -{ - return global_dijkstra->distance[*(u32*)a] - > global_dijkstra->distance[*(u32*)b]; -} - -/* The heap move operator for Dijkstra search. */ -static void dijkstra_item_mover(void *const dst, const void *const src) -{ - u32 src_idx = *(u32*)src; - *(u32*)dst = src_idx; - - // we keep track of the pointer position of each element in the heap, - // for easy update. - global_dijkstra->heapptr[src_idx] = dst; -} - -/* Allocation of resources for the heap. */ -struct dijkstra *dijkstra_new(const tal_t *ctx, size_t max_num_nodes) -{ - struct dijkstra *dijkstra = tal(ctx, struct dijkstra); - - dijkstra->distance = tal_arr(dijkstra,s64,max_num_nodes); - dijkstra->base = tal_arr(dijkstra,u32,max_num_nodes); - dijkstra->heapptr = tal_arrz(dijkstra,u32*,max_num_nodes); - - dijkstra->heapsize=0; - - dijkstra->gheap_ctx.fanout=2; - dijkstra->gheap_ctx.page_chunks=1024; - dijkstra->gheap_ctx.item_size=sizeof(dijkstra->base[0]); - dijkstra->gheap_ctx.less_comparer=dijkstra_less_comparer; - dijkstra->gheap_ctx.less_comparer_ctx=NULL; - dijkstra->gheap_ctx.item_mover=dijkstra_item_mover; - - return dijkstra; -} - - -void dijkstra_init(struct dijkstra *dijkstra) -{ - const size_t max_num_nodes = tal_count(dijkstra->distance); - dijkstra->heapsize=0; - for(size_t i=0;idistance[i]=INFINITE; - dijkstra->heapptr[i] = NULL; - } -} -size_t dijkstra_size(const struct dijkstra *dijkstra) -{ - return dijkstra->heapsize; -} - -size_t dijkstra_maxsize(const struct dijkstra *dijkstra) -{ - return tal_count(dijkstra->distance); -} - -static void dijkstra_append(struct dijkstra *dijkstra, u32 node_idx, s64 distance) -{ - assert(dijkstra_size(dijkstra) < dijkstra_maxsize(dijkstra)); - assert(node_idx < dijkstra_maxsize(dijkstra)); - - const size_t pos = dijkstra->heapsize; - - dijkstra->base[pos]=node_idx; - dijkstra->distance[node_idx]=distance; - dijkstra->heapptr[node_idx] = &(dijkstra->base[pos]); - dijkstra->heapsize++; -} - -void dijkstra_update(struct dijkstra *dijkstra, u32 node_idx, s64 distance) -{ - assert(node_idx < dijkstra_maxsize(dijkstra)); - - if(!dijkstra->heapptr[node_idx]) - { - // not in the heap - dijkstra_append(dijkstra, node_idx,distance); - global_dijkstra = dijkstra; - gheap_restore_heap_after_item_increase( - &dijkstra->gheap_ctx, - dijkstra->base, - dijkstra->heapsize, - dijkstra->heapptr[node_idx] - - dijkstra->base); - global_dijkstra = NULL; - return; - } - - if(dijkstra->distance[node_idx] > distance) - { - // distance decrease - dijkstra->distance[node_idx] = distance; - - global_dijkstra = dijkstra; - gheap_restore_heap_after_item_increase( - &dijkstra->gheap_ctx, - dijkstra->base, - dijkstra->heapsize, - dijkstra->heapptr[node_idx] - - dijkstra->base); - global_dijkstra = NULL; - }else - { - // distance increase - dijkstra->distance[node_idx] = distance; - - global_dijkstra = dijkstra; - gheap_restore_heap_after_item_decrease( - &dijkstra->gheap_ctx, - dijkstra->base, - dijkstra->heapsize, - dijkstra->heapptr[node_idx] - - dijkstra->base); - global_dijkstra = NULL; - - } - // assert(gheap_is_heap(&dijkstra->gheap_ctx, - // dijkstra->base, - // dijkstra_size())); -} - -u32 dijkstra_top(const struct dijkstra *dijkstra) -{ - return dijkstra->base[0]; -} - -bool dijkstra_empty(const struct dijkstra *dijkstra) -{ - return dijkstra->heapsize==0; -} - -void dijkstra_pop(struct dijkstra *dijkstra) -{ - if(dijkstra->heapsize==0) - return; - - const u32 top = dijkstra_top(dijkstra); - assert(dijkstra->heapptr[top]==dijkstra->base); - - global_dijkstra = dijkstra; - gheap_pop_heap( - &dijkstra->gheap_ctx, - dijkstra->base, - dijkstra->heapsize--); - global_dijkstra = NULL; - - dijkstra->heapptr[top]=NULL; -} - -const s64* dijkstra_distance_data(const struct dijkstra *dijkstra) -{ - return dijkstra->distance; -} diff --git a/plugins/renepay/dijkstra.h b/plugins/renepay/dijkstra.h deleted file mode 100644 index bfff1d1dd288..000000000000 --- a/plugins/renepay/dijkstra.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef LIGHTNING_PLUGINS_RENEPAY_DIJKSTRA_H -#define LIGHTNING_PLUGINS_RENEPAY_DIJKSTRA_H -#include "config.h" -#include -#include -#include - -/* Allocation of resources for the heap. */ -struct dijkstra *dijkstra_new(const tal_t *ctx, size_t max_num_nodes); - -/* Initialization of the heap for a new Dijkstra search. */ -void dijkstra_init(struct dijkstra *dijkstra); - -/* Inserts a new element in the heap. If node_idx was already in the heap then - * its distance value is updated. */ -void dijkstra_update(struct dijkstra *dijkstra, u32 node_idx, s64 distance); - -u32 dijkstra_top(const struct dijkstra *dijkstra); -bool dijkstra_empty(const struct dijkstra *dijkstra); -void dijkstra_pop(struct dijkstra *dijkstra); - -const s64* dijkstra_distance_data(const struct dijkstra *dijkstra); - -/* Number of elements on the heap. */ -size_t dijkstra_size(const struct dijkstra *dijkstra); - -/* Maximum number of elements the heap can host */ -size_t dijkstra_maxsize(const struct dijkstra *dijkstra); - -#endif /* LIGHTNING_PLUGINS_RENEPAY_DIJKSTRA_H */ diff --git a/plugins/renepay/disabledmap.c b/plugins/renepay/disabledmap.c deleted file mode 100644 index 68492eced369..000000000000 --- a/plugins/renepay/disabledmap.c +++ /dev/null @@ -1,125 +0,0 @@ -#include "config.h" -#include - -struct disabledmap *disabledmap_new(const tal_t *ctx) -{ - struct disabledmap *obj = tal(ctx, struct disabledmap); - if (!obj) - return NULL; - - obj->disabled_map = tal(obj, struct scidd_map); - obj->warned_map = tal(obj, struct scidd_map); - obj->disabled_ctx = tal(obj, tal_t); - obj->warned_ctx = tal(obj, tal_t); - obj->disabled_nodes = tal_arr(obj, struct node_id, 0); - - if (!obj->disabled_map || !obj->warned_map || !obj->disabled_nodes || - !obj->disabled_ctx || !obj->warned_ctx) - return tal_free(obj); - - scidd_map_init(obj->disabled_map); - scidd_map_init(obj->warned_map); - return obj; -} - -// FIXME: check success -void disabledmap_reset(struct disabledmap *p) -{ - /* This will remove and free every element in the maps. */ - p->warned_ctx = tal_free(p->warned_ctx); - p->disabled_ctx = tal_free(p->disabled_ctx); - - tal_resize(&p->disabled_nodes, 0); - - p->disabled_ctx = tal(p, tal_t); - p->warned_ctx = tal(p, tal_t); -} - -static void remove_scidd(struct short_channel_id_dir *scidd, - struct scidd_map *map) -{ - scidd_map_del(map, scidd); -} - -// FIXME: check success -void disabledmap_add_channel(struct disabledmap *p, - struct short_channel_id_dir scidd) -{ - struct short_channel_id_dir *ptr_scidd = - scidd_map_get(p->disabled_map, scidd); - if (ptr_scidd) { - /* htable allows for duplicates, but we don't want duplicates. - */ - return; - } - ptr_scidd = - tal_dup(p->disabled_ctx, struct short_channel_id_dir, &scidd); - scidd_map_add(p->disabled_map, ptr_scidd); - tal_add_destructor2(ptr_scidd, remove_scidd, p->disabled_map); -} - -// FIXME: check success -void disabledmap_warn_channel(struct disabledmap *p, - struct short_channel_id_dir scidd) -{ - struct short_channel_id_dir *ptr_scidd = - scidd_map_get(p->warned_map, scidd); - if (ptr_scidd) { - /* htable allows for duplicates, but we don't want duplicates. - */ - return; - } - ptr_scidd = tal_dup(p->warned_ctx, struct short_channel_id_dir, &scidd); - scidd_map_add(p->warned_map, ptr_scidd); - tal_add_destructor2(ptr_scidd, remove_scidd, p->warned_map); -} - -// FIXME: check success -void disabledmap_add_node(struct disabledmap *p, struct node_id node) -{ - tal_arr_expand(&p->disabled_nodes, node); -} - -bool disabledmap_channel_is_warned(struct disabledmap *p, - struct short_channel_id_dir scidd) -{ - return scidd_map_get(p->warned_map, scidd) != NULL; -} - -bitmap *tal_disabledmap_get_bitmap(const tal_t *ctx, struct disabledmap *p, - const struct gossmap *gossmap) -{ - bitmap *disabled = tal_arrz( - ctx, bitmap, 2 * BITMAP_NWORDS(gossmap_max_chan_idx(gossmap))); - if (!disabled) - return NULL; - - /* Disable every channel in the list of disabled scids. */ - struct scidd_map_iter it; - for(struct short_channel_id_dir *scidd = scidd_map_first(p->disabled_map,&it); - scidd; - scidd = scidd_map_next(p->disabled_map, &it)){ - - struct gossmap_chan *c = - gossmap_find_chan(gossmap, &scidd->scid); - if (c) - bitmap_set_bit(disabled, - gossmap_chan_idx(gossmap, c) * 2 + - scidd->dir); - } - - /* Disable all channels that lead to a disabled node. */ - for (size_t i = 0; i < tal_count(p->disabled_nodes); i++) { - const struct gossmap_node *node = - gossmap_find_node(gossmap, &p->disabled_nodes[i]); - - for (size_t j = 0; j < node->num_chans; j++) { - int half; - const struct gossmap_chan *c = - gossmap_nth_chan(gossmap, node, j, &half); - bitmap_set_bit(disabled, - gossmap_chan_idx(gossmap, c) * 2 + half); - } - } - return disabled; -} diff --git a/plugins/renepay/disabledmap.h b/plugins/renepay/disabledmap.h deleted file mode 100644 index 2dea547b076f..000000000000 --- a/plugins/renepay/disabledmap.h +++ /dev/null @@ -1,64 +0,0 @@ -#ifndef LIGHTNING_PLUGINS_RENEPAY_DISABLEDMAP_H -#define LIGHTNING_PLUGINS_RENEPAY_DISABLEDMAP_H - -#include "config.h" -#include -#include -#include -#include -#include - -static inline size_t hash_scidd(const struct short_channel_id_dir scidd) -{ - /* scids cost money to generate, so simple hash works here. Letting same - * scid with two directions collide. */ - return (scidd.scid.u64 >> 32) ^ (scidd.scid.u64 >> 16) ^ scidd.scid.u64; -} - -static inline struct short_channel_id_dir -self_scidd(const struct short_channel_id_dir *self) -{ - return *self; -} - -static inline bool -my_short_channel_id_dir_eq(const struct short_channel_id_dir *scidd_a, - const struct short_channel_id_dir scidd_b) -{ - return short_channel_id_eq(scidd_a->scid, scidd_b.scid) && - scidd_a->dir == scidd_b.dir; -} - -/* A htable for short_channel_id_dir, the structure itself is the element key. - */ -HTABLE_DEFINE_TYPE(struct short_channel_id_dir, self_scidd, hash_scidd, - my_short_channel_id_dir_eq, scidd_map); - -struct disabledmap { - /* Channels we decided to disable for various reasons. */ - struct scidd_map *disabled_map; - tal_t *disabled_ctx; - - /* Channels that we flagged for failures. If warned two times we will - * disable it. */ - struct scidd_map *warned_map; - tal_t *warned_ctx; - - /* nodes we disable */ - // FIXME: use a map also for nodes - struct node_id *disabled_nodes; -}; - -void disabledmap_reset(struct disabledmap *p); -struct disabledmap *disabledmap_new(const tal_t *ctx); -void disabledmap_add_channel(struct disabledmap *p, - struct short_channel_id_dir scidd); -void disabledmap_warn_channel(struct disabledmap *p, - struct short_channel_id_dir scidd); -void disabledmap_add_node(struct disabledmap *p, struct node_id node); -bool disabledmap_channel_is_warned(struct disabledmap *p, - struct short_channel_id_dir scidd); -bitmap *tal_disabledmap_get_bitmap(const tal_t *ctx, struct disabledmap *p, - const struct gossmap *gossmap); - -#endif /* LIGHTNING_PLUGINS_RENEPAY_DISABLEDMAP_H */ diff --git a/plugins/renepay/errorcodes.c b/plugins/renepay/errorcodes.c deleted file mode 100644 index fcf21b15f898..000000000000 --- a/plugins/renepay/errorcodes.c +++ /dev/null @@ -1,29 +0,0 @@ -#include "config.h" -#include -#include -#include - -const char *renepay_errorcode_name(enum renepay_errorcode e) -{ - static char invalidbuf[sizeof("INVALID ") + STR_MAX_CHARS(e)]; - - switch (e) { - case RENEPAY_NOERROR: - return "RENEPAY_NOERROR"; - case RENEPAY_AMOUNT_OVERFLOW: - return "RENEPAY_AMOUNT_OVERFLOW"; - case RENEPAY_CHANNEL_NOT_FOUND: - return "RENEPAY_CHANNEL_NOT_FOUND"; - case RENEPAY_BAD_CHANNEL: - return "RENEPAY_BAD_CHANNEL"; - case RENEPAY_BAD_ALLOCATION: - return "RENEPAY_BAD_ALLOCATION"; - case RENEPAY_PRECONDITION_ERROR: - return "RENEPAY_PRECONDITION_ERROR"; - case RENEPAY_UNEXPECTED: - return "RENEPAY_UNEXPECTED"; - } - - snprintf(invalidbuf, sizeof(invalidbuf), "INVALID %i", e); - return invalidbuf; -} diff --git a/plugins/renepay/errorcodes.h b/plugins/renepay/errorcodes.h deleted file mode 100644 index 6b9c09db7752..000000000000 --- a/plugins/renepay/errorcodes.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef LIGHTNING_PLUGINS_RENEPAY_ERRORCODES_H -#define LIGHTNING_PLUGINS_RENEPAY_ERRORCODES_H - -#include "config.h" - -/* Common types of failures for low level functions in renepay. */ -enum renepay_errorcode { - RENEPAY_NOERROR = 0, - - RENEPAY_AMOUNT_OVERFLOW, - RENEPAY_CHANNEL_NOT_FOUND, - RENEPAY_BAD_CHANNEL, - RENEPAY_BAD_ALLOCATION, - RENEPAY_PRECONDITION_ERROR, - RENEPAY_UNEXPECTED, -}; - -const char *renepay_errorcode_name(enum renepay_errorcode e); - -#endif /* LIGHTNING_PLUGINS_RENEPAY_ERRORCODES_H */ diff --git a/plugins/renepay/flow.c b/plugins/renepay/flow.c deleted file mode 100644 index 6d4d25aa1ccd..000000000000 --- a/plugins/renepay/flow.c +++ /dev/null @@ -1,489 +0,0 @@ -#include "config.h" -#include -#include -#include -#include -#include -#include -#include -#include - -#ifndef SUPERVERBOSE -#define SUPERVERBOSE(...) -#else -#define SUPERVERBOSE_ENABLED 1 -#endif - -struct amount_msat *tal_flow_amounts(const tal_t *ctx, const struct flow *flow, - bool compute_fees) -{ - const size_t pathlen = tal_count(flow->path); - struct amount_msat *amounts = tal_arr(ctx, struct amount_msat, pathlen); - amounts[pathlen - 1] = flow->amount; - - for (int i = (int)pathlen - 2; i >= 0; i--) { - const struct half_chan *h = flow_edge(flow, i + 1); - amounts[i] = amounts[i + 1]; - if (compute_fees && - !amount_msat_add_fee(&amounts[i], h->base_fee, - h->proportional_fee)) - goto function_fail; - } - - return amounts; - -function_fail: - return tal_free(amounts); -} - -const char *fmt_flows(const tal_t *ctx, const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map, - struct flow **flows) -{ - tal_t *this_ctx = tal(ctx, tal_t); - char *buff = tal_fmt(ctx, "%zu subflows\n", tal_count(flows)); - for (size_t i = 0; i < tal_count(flows); i++) { - struct amount_msat fee, delivered; - tal_append_fmt(&buff, " "); - for (size_t j = 0; j < tal_count(flows[i]->path); j++) { - struct short_channel_id scid = - gossmap_chan_scid(gossmap, flows[i]->path[j]); - tal_append_fmt(&buff, "%s%s", j ? "->" : "", - fmt_short_channel_id(this_ctx, scid)); - } - delivered = flows[i]->amount; - if (!flow_fee(&fee, flows[i])) { - abort(); - } - tal_append_fmt(&buff, " prob %.2f, %s delivered with fee %s\n", - flows[i]->success_prob, - fmt_amount_msat(this_ctx, delivered), - fmt_amount_msat(this_ctx, fee)); - } - - tal_free(this_ctx); - return buff; -} - -/* Returns the greatest amount we can deliver to the destination using this - * route. It takes into account the current knowledge, pending HTLC, - * htlc_max and fees. - * - * It fails if the maximum that we can - * deliver at node i is smaller than the minimum required to forward the least - * amount greater than zero to the next node. */ -enum renepay_errorcode -flow_maximum_deliverable(struct amount_msat *max_deliverable, - const struct flow *flow, - const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map, - const struct gossmap_chan **bad_channel) -{ - assert(tal_count(flow->path) > 0); - assert(tal_count(flow->dirs) > 0); - assert(tal_count(flow->path) == tal_count(flow->dirs)); - struct amount_msat x; - enum renepay_errorcode err; - - err = channel_liquidity(&x, gossmap, chan_extra_map, flow->path[0], - flow->dirs[0]); - if(err){ - if(bad_channel)*bad_channel = flow->path[0]; - return err; - } - x = amount_msat_min(x, gossmap_chan_htlc_max(flow->path[0], flow->dirs[0])); - - if(amount_msat_is_zero(x)) - { - if(bad_channel)*bad_channel = flow->path[0]; - return RENEPAY_BAD_CHANNEL; - } - - for (size_t i = 1; i < tal_count(flow->path); ++i) { - // ith node can forward up to 'liquidity_cap' because of the ith - // channel liquidity bound - struct amount_msat liquidity_cap; - - err = channel_liquidity(&liquidity_cap, gossmap, chan_extra_map, - flow->path[i], flow->dirs[i]); - if(err) { - if(bad_channel)*bad_channel = flow->path[i]; - return err; - } - - /* ith node can receive up to 'x', therefore he will not forward - * more than 'forward_cap' that we compute below inverting the - * fee equation. */ - struct amount_msat forward_cap; - err = channel_maximum_forward(&forward_cap, flow->path[i], - flow->dirs[i], x); - if(err) - { - if(bad_channel)*bad_channel = flow->path[i]; - return err; - } - struct amount_msat x_new = - amount_msat_min(forward_cap, liquidity_cap); - x_new = amount_msat_min( - x_new, gossmap_chan_htlc_max(flow->path[i], flow->dirs[i])); - - /* safety check: amounts decrease along the route */ - assert(amount_msat_less_eq(x_new, x)); - - if(amount_msat_is_zero(x_new)) - { - if(bad_channel)*bad_channel = flow->path[i]; - return RENEPAY_BAD_CHANNEL; - } - - /* safety check: the max liquidity in the next hop + fees cannot - be greater than the max liquidity in the current hop, IF the - next hop is non-zero. */ - struct amount_msat x_check = x_new; - assert( - amount_msat_add_fee(&x_check, flow_edge(flow, i)->base_fee, - flow_edge(flow, i)->proportional_fee)); - assert(amount_msat_less_eq(x_check, x)); - - x = x_new; - } - assert(!amount_msat_is_zero(x)); - *max_deliverable = x; - return RENEPAY_NOERROR; -} - -/* Returns the smallest amount we can send so that the destination can get one - * HTLC of any size. It takes into account htlc_min and fees. - * */ -// static enum renepay_errorcode -// flow_minimum_sendable(struct amount_msat *min_sendable UNUSED, -// const struct flow *flow UNUSED, -// const struct gossmap *gossmap UNUSED, -// struct chan_extra_map *chan_extra_map UNUSED) -// { -// // TODO -// return RENEPAY_NOERROR; -// } - -/* How much do we deliver to destination using this set of routes */ -bool flowset_delivers(struct amount_msat *delivers, struct flow **flows) -{ - struct amount_msat final = AMOUNT_MSAT(0); - for (size_t i = 0; i < tal_count(flows); i++) { - if (!amount_msat_accumulate(&final, flows[i]->amount)) - return false; - } - *delivers = final; - return true; -} - -/* FIXME: pass a pointer to const here */ -size_t flowset_size(struct flow **flows) -{ - size_t size = 0; - for (size_t i = 0; i < tal_count(flows); i++) - size += tal_count(flows[i]->path); - return size; -} - -/* Checks if the flows satisfy the liquidity bounds imposed by the known maximum - * liquidity and pending HTLCs. - * - * FIXME The function returns false even in the case of failure. The caller has - * no way of knowing the difference between a failure of evaluation and a - * negative answer. */ -// static bool check_liquidity_bounds(struct flow **flows, -// const struct gossmap *gossmap, -// struct chan_extra_map *chan_extra_map) -// { -// bool check = true; -// for (size_t i = 0; i < tal_count(flows); ++i) { -// struct amount_msat max_deliverable; -// if (!flow_maximum_deliverable(&max_deliverable, flows[i], -// gossmap, chan_extra_map)) -// return false; -// struct amount_msat delivers = flow_delivers(flows[i]); -// check &= amount_msat_less_eq(delivers, max_deliverable); -// } -// return check; -// } - -/* Compute the prob. of success of a set of concurrent set of flows. - * - * IMPORTANT: this is not simply the multiplication of the prob. of success of - * all of them, because they're not independent events. A flow that passes - * through a channel c changes that channel's liquidity and then if another flow - * passes through that same channel the previous liquidity change must be taken - * into account. - * - * P(A and B) != P(A) * P(B), - * - * but - * - * P(A and B) = P(A) * P(B | A) - * - * also due to the linear form of P() we have - * - * P(A and B) = P(A + B) - * */ -struct chan_inflight_flow -{ - struct amount_msat half[2]; -}; - -/* @ctx: allocator - * @flows: flows for which the probability is computed - * @gossmap: gossip - * @chan_extra_map: knowledge - * @compute_fees: compute fees along the way or not - * @fail: if a failure occurs, returns a message to the caller - * */ -// TODO(eduardo): here chan_extra_map should be const -// TODO(eduardo): here flows should be const -double flowset_probability(const tal_t *ctx, struct flow **flows, - const struct gossmap *const gossmap, - struct chan_extra_map *chan_extra_map, - bool compute_fees, - char **fail) -{ - assert(flows); - assert(gossmap); - assert(chan_extra_map); - tal_t *this_ctx = tal(ctx, tal_t); - double prob = 1.0; - - // TODO(eduardo): should it be better to use a map instead of an array - // here? - const size_t max_num_chans = gossmap_max_chan_idx(gossmap); - struct chan_inflight_flow *in_flight = - tal_arr(this_ctx, struct chan_inflight_flow, max_num_chans); - - if (!in_flight) { - if (fail) - *fail = tal_fmt(ctx, "failed to allocate memory"); - goto function_fail; - } - - for (size_t i = 0; i < max_num_chans; ++i) { - in_flight[i].half[0] = in_flight[i].half[1] = AMOUNT_MSAT(0); - } - - for (size_t i = 0; i < tal_count(flows); ++i) { - const struct flow *f = flows[i]; - const size_t pathlen = tal_count(f->path); - struct amount_msat *amounts = - tal_flow_amounts(this_ctx, f, compute_fees); - if (!amounts) - { - if (fail) - *fail = tal_fmt( - ctx, - "failed to compute amounts along the path"); - goto function_fail; - } - - for (size_t j = 0; j < pathlen; ++j) { - const struct chan_extra_half *h = - get_chan_extra_half_by_chan(gossmap, chan_extra_map, - f->path[j], f->dirs[j]); - if (!h) { - if (fail) - *fail = tal_fmt( - ctx, - "channel not found in chan_extra_map"); - goto function_fail; - } - const u32 c_idx = gossmap_chan_idx(gossmap, f->path[j]); - const int c_dir = f->dirs[j]; - - const struct amount_msat deliver = amounts[j]; - - struct amount_msat prev_flow, all_inflight; - if (!amount_msat_add(&prev_flow, h->htlc_total, - in_flight[c_idx].half[c_dir]) || - !amount_msat_add(&all_inflight, prev_flow, - deliver)) { - if (fail) - *fail = tal_fmt( - ctx, - "in-flight amount_msat overflow"); - goto function_fail; - } - - if (!amount_msat_less_eq(all_inflight, h->known_max)) { - if (fail) - *fail = tal_fmt( - ctx, - "in-flight (%s) exceeds known_max " - "(%s)", - fmt_amount_msat(ctx, all_inflight), - fmt_amount_msat(ctx, h->known_max)); - goto function_fail; - } - - double edge_prob = edge_probability( - h->known_min, h->known_max, prev_flow, deliver); - - if (edge_prob < 0) { - if (fail) - *fail = tal_fmt(ctx, - "edge_probability failed"); - goto function_fail; - } - prob *= edge_prob; - - if (!amount_msat_add(&in_flight[c_idx].half[c_dir], - in_flight[c_idx].half[c_dir], - deliver)) { - if (fail) - *fail = tal_fmt( - ctx, "in-flight amount_msat overflow"); - goto function_fail; - } - } - } - tal_free(this_ctx); - return prob; - - function_fail: - tal_free(this_ctx); - return -1; -} - -bool flow_spend(struct amount_msat *ret, struct flow *flow) -{ - assert(ret); - assert(flow); - const size_t pathlen = tal_count(flow->path); - struct amount_msat spend = flow->amount; - - for (int i = (int)pathlen - 2; i >= 0; i--) { - const struct half_chan *h = flow_edge(flow, i + 1); - if (!amount_msat_add_fee(&spend, h->base_fee, - h->proportional_fee)) - goto function_fail; - } - - *ret = spend; - return true; - -function_fail: - return false; -} - -bool flow_fee(struct amount_msat *ret, struct flow *flow) -{ - assert(ret); - assert(flow); - struct amount_msat fee; - struct amount_msat spend; - if (!flow_spend(&spend, flow)) - goto function_fail; - if (!amount_msat_sub(&fee, spend, flow->amount)) - goto function_fail; - - *ret = fee; - return true; - -function_fail: - return false; -} - -bool flowset_fee(struct amount_msat *ret, struct flow **flows) -{ - assert(ret); - assert(flows); - struct amount_msat fee = AMOUNT_MSAT(0); - for (size_t i = 0; i < tal_count(flows); i++) { - struct amount_msat this_fee; - if (!flow_fee(&this_fee, flows[i])) - return false; - if (!amount_msat_add(&fee, this_fee, fee)) - return false; - } - *ret = fee; - return true; -} - -/* Helper to access the half chan at flow index idx */ -const struct half_chan *flow_edge(const struct flow *flow, size_t idx) -{ - assert(flow); - assert(idx < tal_count(flow->path)); - return &flow->path[idx]->half[flow->dirs[idx]]; -} - -/* Assign the delivered amount to the flow if it fits - the path maximum capacity. */ -bool flow_assign_delivery(struct flow *flow, const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map, - struct amount_msat requested_amount) -{ - struct amount_msat max_deliverable = AMOUNT_MSAT(0); - if (flow_maximum_deliverable(&max_deliverable, flow, gossmap, - chan_extra_map, NULL)) - return false; - assert(!amount_msat_is_zero(max_deliverable)); - flow->amount = amount_msat_min(requested_amount, max_deliverable); - return true; -} - -/* Helper function to find the success_prob for a single flow - * - * IMPORTANT: flow->success_prob is misleading, because that's the prob. of - * success provided that there are no other flows in the current MPP flow set. - * */ -double flow_probability(struct flow *flow, const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map, - bool compute_fees) -{ - assert(flow); - assert(gossmap); - assert(chan_extra_map); - const size_t pathlen = tal_count(flow->path); - struct amount_msat spend = flow->amount; - double prob = 1.0; - - for (int i = (int)pathlen - 1; i >= 0; i--) { - const struct half_chan *h = flow_edge(flow, i); - const struct chan_extra_half *eh = get_chan_extra_half_by_chan( - gossmap, chan_extra_map, flow->path[i], flow->dirs[i]); - - prob *= edge_probability(eh->known_min, eh->known_max, - eh->htlc_total, spend); - - if (prob < 0) - goto function_fail; - if (compute_fees && !amount_msat_add_fee(&spend, h->base_fee, - h->proportional_fee)) - goto function_fail; - } - - return prob; - -function_fail: - return -1.; -} - -u64 flow_delay(const struct flow *flow) -{ - u64 delay = 0; - for (size_t i = 0; i < tal_count(flow->path); i++) - delay += flow_edge(flow, i)->delay; - return delay; -} - -u64 flows_worst_delay(struct flow **flows) -{ - u64 maxdelay = 0; - for (size_t i = 0; i < tal_count(flows); i++) { - u64 delay = flow_delay(flows[i]); - if (delay > maxdelay) - maxdelay = delay; - } - return maxdelay; -} - -#ifndef SUPERVERBOSE_ENABLED -#undef SUPERVERBOSE -#endif diff --git a/plugins/renepay/flow.h b/plugins/renepay/flow.h deleted file mode 100644 index 959eb42f3f64..000000000000 --- a/plugins/renepay/flow.h +++ /dev/null @@ -1,95 +0,0 @@ -#ifndef LIGHTNING_PLUGINS_RENEPAY_FLOW_H -#define LIGHTNING_PLUGINS_RENEPAY_FLOW_H -#include "config.h" -#include -#include -#include -#include -#include -#include - -/* An actual partial flow. */ -struct flow { - const struct gossmap_chan **path; - /* The directions to traverse. */ - int *dirs; - /* Amounts for this flow (fees mean this shrinks across path). */ - double success_prob; - struct amount_msat amount; -}; - -const char *fmt_flows(const tal_t *ctx, const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map, - struct flow **flows); - -/* Helper to access the half chan at flow index idx */ -const struct half_chan *flow_edge(const struct flow *flow, size_t idx); - -/* A big number, meaning "don't bother" (not infinite, since you may add) */ -#define FLOW_INF_COST 100000000.0 - -/* Cost function to send @f msat through @c in direction @dir, - * given we already have a flow of prev_flow. */ -double flow_edge_cost(const struct gossmap *gossmap, - const struct gossmap_chan *c, int dir, - const struct amount_msat known_min, - const struct amount_msat known_max, - struct amount_msat prev_flow, - struct amount_msat f, - double mu, - double basefee_penalty, - double delay_riskfactor); - -/* Compute the prob. of success of a set of concurrent set of flows. */ -double flowset_probability(const tal_t *ctx, struct flow **flows, - const struct gossmap *const gossmap, - struct chan_extra_map *chan_extra_map, - bool compute_fees, char **fail); - -/* How much do we need to send to make this flow arrive. */ -bool flow_spend(struct amount_msat *ret, struct flow *flow); - -/* How much do we pay in fees to make this flow arrive. */ -bool flow_fee(struct amount_msat *ret, struct flow *flow); - -bool flowset_fee(struct amount_msat *fee, struct flow **flows); - -bool flowset_delivers(struct amount_msat *delivers, struct flow **flows); - -/* how many channels are being used */ -size_t flowset_size(struct flow **flows); - -static inline struct amount_msat flow_delivers(const struct flow *flow) -{ - return flow->amount; -} - -struct amount_msat *tal_flow_amounts(const tal_t *ctx, const struct flow *flow, - bool compute_fees); - -enum renepay_errorcode -flow_maximum_deliverable(struct amount_msat *max_deliverable, - const struct flow *flow, - const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map, - const struct gossmap_chan **bad_channel); - -/* Assign the delivered amount to the flow if it fits - the path maximum capacity. */ -bool flow_assign_delivery(struct flow *flow, const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map, - struct amount_msat requested_amount); - -double flow_probability(struct flow *flow, const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map, - bool compute_fees); - -u64 flow_delay(const struct flow *flow); -u64 flows_worst_delay(struct flow **flows); - -struct flow ** -flows_ensure_liquidity_constraints(const tal_t *ctx, struct flow **flows TAKES, - const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map); - -#endif /* LIGHTNING_PLUGINS_RENEPAY_FLOW_H */ diff --git a/plugins/renepay/json.c b/plugins/renepay/json.c index 4db97bd93a47..296120852847 100644 --- a/plugins/renepay/json.c +++ b/plugins/renepay/json.c @@ -64,6 +64,7 @@ struct route *tal_route_from_json(const tal_t *ctx, const char *buf, route->hops = NULL; route->final_msg = NULL; route->final_error = LIGHTNINGD; + route->is_reserved = false; return route; fail: @@ -262,6 +263,24 @@ void json_add_payment(struct json_stream *s, const struct payment *payment) // - number of parts? } +void json_add_route_hops(struct json_stream *js, const char *fieldname, + const struct route_hop *hops) +{ + const size_t pathlen = tal_count(hops); + json_array_start(js, fieldname); + for (size_t j = 0; j < pathlen; j++) { + const struct route_hop *h = &hops[j]; + json_object_start(js, NULL); + json_add_node_id(js, "id", &h->node_id); + json_add_short_channel_id(js, "channel", h->scid); + json_add_amount_msat(js, "amount_msat", h->amount); + json_add_num(js, "direction", h->direction); + json_add_u32(js, "delay", h->delay); + json_object_end(js); + } + json_array_end(js); +} + void json_add_route(struct json_stream *js, const struct route *route, const struct payment *payment) { @@ -273,22 +292,7 @@ void json_add_route(struct json_stream *js, const struct route *route, assert(route->hops); const size_t pathlen = tal_count(route->hops); - - json_array_start(js, "route"); - /* An empty route means a payment to oneself, pathlen=0 */ - for (size_t j = 0; j < pathlen; j++) { - const struct route_hop *hop = &route->hops[j]; - - json_object_start(js, NULL); - json_add_node_id(js, "id", &hop->node_id); - json_add_short_channel_id(js, "channel", hop->scid); - json_add_amount_msat(js, "amount_msat", hop->amount); - json_add_num(js, "direction", hop->direction); - json_add_u32(js, "delay", hop->delay); - json_add_string(js, "style", "tlv"); - json_object_end(js); - } - json_array_end(js); + json_add_route_hops(js, "route", route->hops); json_add_sha256(js, "payment_hash", &pinfo->payment_hash); if (pinfo->payment_secret) diff --git a/plugins/renepay/json.h b/plugins/renepay/json.h index d08749c2c0f5..4d3fadb1b4fe 100644 --- a/plugins/renepay/json.h +++ b/plugins/renepay/json.h @@ -17,6 +17,9 @@ struct payment_result *tal_sendpay_result_from_json(const tal_t *ctx, void json_add_payment(struct json_stream *s, const struct payment *payment); +void json_add_route_hops(struct json_stream *js, const char *fieldname, + const struct route_hop *hops); + void json_add_route(struct json_stream *s, const struct route *route, const struct payment *payment); diff --git a/plugins/renepay/main.c b/plugins/renepay/main.c index f2bee7af98d1..dc3a77e9da56 100644 --- a/plugins/renepay/main.c +++ b/plugins/renepay/main.c @@ -29,8 +29,6 @@ struct pay_plugin *pay_plugin; static void memleak_mark(struct plugin *p, struct htable *memtable) { memleak_scan_obj(memtable, pay_plugin); - memleak_scan_htable(memtable, - &pay_plugin->uncertainty->chan_extra_map->raw); memleak_scan_htable(memtable, &pay_plugin->payment_map->raw); memleak_scan_htable(memtable, &pay_plugin->pending_routes->raw); } @@ -82,14 +80,6 @@ static const char *init(struct plugin *p, plugin_log(p, LOG_DBG, "gossmap ignored %zu channel updates", num_channel_updates_rejected); - pay_plugin->uncertainty = uncertainty_new(pay_plugin); - int skipped_count = - uncertainty_update(pay_plugin->uncertainty, pay_plugin->gossmap); - if (skipped_count) - plugin_log(pay_plugin->plugin, LOG_UNUSUAL, - "%s: uncertainty was updated but %d channels have " - "been ignored.", - __func__, skipped_count); plugin_set_memleak_handler(p, memleak_mark); return NULL; @@ -157,6 +147,7 @@ static struct command_result * payment_start(struct payment *p) p->status = PAYMENT_PENDING; plugin_log(pay_plugin->plugin, LOG_DBG, "Starting renepay"); p->exec_state = 0; + p->pending_rpcs = 0; return payment_continue(p); } diff --git a/plugins/renepay/mcf.c b/plugins/renepay/mcf.c deleted file mode 100644 index 7d2f1490300a..000000000000 --- a/plugins/renepay/mcf.c +++ /dev/null @@ -1,1860 +0,0 @@ -#include "config.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* # Optimal payments - * - * In this module we reduce the routing optimization problem to a linear - * cost optimization problem and find a solution using MCF algorithms. - * The optimization of the routing itself doesn't need a precise numerical - * solution, since we can be happy near optimal results; e.g. paying 100 msat or - * 101 msat for fees doesn't make any difference if we wish to deliver 1M sats. - * On the other hand, we are now also considering Pickhard's - * [1] model to improve payment reliability, - * hence our optimization moves to a 2D space: either we like to maximize the - * probability of success of a payment or minimize the routing fees, or - * alternatively we construct a function of the two that gives a good compromise. - * - * Therefore from now own, the definition of optimal is a matter of choice. - * To simplify the API of this module, we think the best way to state the - * problem is: - * - * Find a routing solution that pays the least of fees while keeping - * the probability of success above a certain value `min_probability`. - * - * - * # Fee Cost - * - * Routing fees is non-linear function of the payment flow x, that's true even - * without the base fee: - * - * fee_msat = base_msat + floor(millionths*x_msat / 10^6) - * - * We approximate this fee into a linear function by computing a slope `c_fee` such - * that: - * - * fee_microsat = c_fee * x_sat - * - * Function `linear_fee_cost` computes `c_fee` based on the base and - * proportional fees of a channel. - * The final product if microsat because if only - * the proportional fee was considered we can have c_fee = millionths. - * Moving to costs based in msats means we have to either truncate payments - * below 1ksats or estimate as 0 cost for channels with less than 1000ppm. - * - * TODO(eduardo): shall we build a linear cost function in msats? - * - * # Probability cost - * - * The probability of success P of the payment is the product of the prob. of - * success of forwarding parts of the payment over all routing channels. This - * problem is separable if we log it, and since we would like to increase P, - * then we can seek to minimize -log(P), and that's our prob. cost function [1]. - * - * - log P = sum_{i} - log P_i - * - * The probability of success `P_i` of sending some flow `x` on a channel with - * liquidity l in the range a<=l a - * = 1. ; for x <= a - * - * Notice that unlike the similar formula in [1], the one we propose does not - * contain the quantization shot noise for counting states. The formula remains - * valid independently of the liquidity units (sats or msats). - * - * The cost associated to probability P is then -k log P, where k is some - * constant. For k=1 we get the following table: - * - * prob | cost - * ----------- - * 0.01 | 4.6 - * 0.02 | 3.9 - * 0.05 | 3.0 - * 0.10 | 2.3 - * 0.20 | 1.6 - * 0.50 | 0.69 - * 0.80 | 0.22 - * 0.90 | 0.10 - * 0.95 | 0.05 - * 0.98 | 0.02 - * 0.99 | 0.01 - * - * Clearly -log P(x) is non-linear; we try to linearize it piecewise: - * split the channel into 4 arcs representing 4 liquidity regions: - * - * arc_0 -> [0, a) - * arc_1 -> [a, a+(b-a)*f1) - * arc_2 -> [a+(b-a)*f1, a+(b-a)*f2) - * arc_3 -> [a+(b-a)*f2, a+(b-a)*f3) - * - * where f1 = 0.5, f2 = 0.8, f3 = 0.95; - * We fill arc_0's capacity with complete certainty P=1, then if more flow is - * needed we start filling the capacity in arc_1 until the total probability - * of success reaches P=0.5, then arc_2 until P=1-0.8=0.2, and finally arc_3 until - * P=1-0.95=0.05. We don't go further than 5% prob. of success per channel. - - * TODO(eduardo): this channel linearization is hard coded into - * `CHANNEL_PIVOTS`, maybe we can parametrize this to take values from the config file. - * - * With this choice, the slope of the linear cost function becomes: - * - * m_0 = 0 - * m_1 = 1.38 k /(b-a) - * m_2 = 3.05 k /(b-a) - * m_3 = 9.24 k /(b-a) - * - * Notice that one of the assumptions in [2] for the MCF problem is that flows - * and the slope of the costs functions are integer numbers. The only way we - * have at hand to make it so, is to choose a universal value of `k` that scales - * up the slopes so that floor(m_i) is not zero for every arc. - * - * # Combine fee and prob. costs - * - * We attempt to solve the original problem of finding the solution that - * pays the least fees while keeping the prob. of success above a certain value, - * by constructing a cost function which is a linear combination of fee and - * prob. costs. - * TODO(eduardo): investigate how this procedure is justified, - * possibly with the use of Lagrange optimization theory. - * - * At first, prob. and fee costs live in different dimensions, they cannot be - * summed, it's like comparing apples and oranges. - * However we propose to scale the prob. cost by a global factor k that - * translates into the monetization of prob. cost. - * - * k/1000, for instance, becomes the equivalent monetary cost - * of increasing the probability of success by 0.1% for P~100%. - * - * The input parameter `prob_cost_factor` in the function `minflow` is defined - * as the PPM from the delivery amount `T` we are *willing to pay* to increase the - * prob. of success by 0.1%: - * - * k_microsat = floor(1000*prob_cost_factor * T_sat) - * - * Is this enough to make integer prob. cost per unit flow? - * For `prob_cost_factor=10`; i.e. we pay 10ppm for increasing the prob. by - * 0.1%, we get that - * - * -> any arc with (b-a) > 10000 T, will have zero prob. cost, which is - * reasonable because even if all the flow passes through that arc, we get - * a 1.3 T/(b-a) ~ 0.01% prob. of failure at most. - * - * -> if (b-a) ~ 10000 T, then the arc will have unit cost, or just that we - * pay 1 microsat for every sat we send through this arc. - * - * -> it would be desirable to have a high proportional fee when (b-a)~T, - * because prob. of failure start to become very high. - * In this case we get to pay 10000 microsats for every sat. - * - * Once `k` is fixed then we can combine the linear prob. and fee costs, both - * are in monetary units. - * - * Note: with costs in microsats, because slopes represent ppm and flows are in - * sats, then our integer bounds with 64 bits are such that we can move as many - * as 10'000 BTC without overflow: - * - * 10^6 (max ppm) * 10^8 (sats per BTC) * 10^4 = 10^18 - * - * # References - * - * [1] Pickhardt and Richter, https://arxiv.org/abs/2107.05322 - * [2] R.K. Ahuja, T.L. Magnanti, and J.B. Orlin. Network Flows: - * Theory, Algorithms, and Applications. Prentice Hall, 1993. - * - * - * TODO(eduardo) it would be interesting to see: - * how much do we pay for reliability? - * Cost_fee(most reliable solution) - Cost_fee(cheapest solution) - * - * TODO(eduardo): it would be interesting to see: - * how likely is the most reliable path with respect to the cheapest? - * Prob(reliable)/Prob(cheapest) = Exp(Cost_prob(cheapest)-Cost_prob(reliable)) - * - * */ - -#define PARTS_BITS 2 -#define CHANNEL_PARTS (1 << PARTS_BITS) - -// These are the probability intervals we use to decompose a channel into linear -// cost function arcs. -static const double CHANNEL_PIVOTS[]={0,0.5,0.8,0.95}; - -static const s64 INFINITE = INT64_MAX; -static const u64 INFINITE_MSAT = UINT64_MAX; -static const u32 INVALID_INDEX = 0xffffffff; -static const s64 MU_MAX = 128; - -/* Let's try this encoding of arcs: - * Each channel `c` has two possible directions identified by a bit - * `half` or `!half`, and each one of them has to be - * decomposed into 4 liquidity parts in order to - * linearize the cost function, but also to solve MCF - * problem we need to keep track of flows in the - * residual network hence we need for each directed arc - * in the network there must be another arc in the - * opposite direction refered to as it's dual. In total - * 1+2+1 additional bits of information: - * - * (chan_idx)(half)(part)(dual) - * - * That means, for each channel we need to store the - * information of 16 arcs. If we implement a convex-cost - * solver then we can reduce that number to size(half)size(dual)=4. - * - * In the adjacency of a `node` we are going to store - * the outgoing arcs. If we ever need to loop over the - * incoming arcs then we will define a reverse adjacency - * API. - * Then for each outgoing channel `(c,half)` there will - * be 4 parts for the actual residual capacity, hence - * with the dual bit set to 0: - * - * (c,half,0,0) - * (c,half,1,0) - * (c,half,2,0) - * (c,half,3,0) - * - * and also we need to consider the dual arcs - * corresponding to the channel direction `(c,!half)` - * (the dual has reverse direction): - * - * (c,!half,0,1) - * (c,!half,1,1) - * (c,!half,2,1) - * (c,!half,3,1) - * - * These are the 8 outgoing arcs relative to `node` and - * associated with channel `c`. The incoming arcs will - * be: - * - * (c,!half,0,0) - * (c,!half,1,0) - * (c,!half,2,0) - * (c,!half,3,0) - * - * (c,half,0,1) - * (c,half,1,1) - * (c,half,2,1) - * (c,half,3,1) - * - * but they will be stored as outgoing arcs on the peer - * node `next`. - * - * I hope this will clarify my future self when I forget. - * - * */ - -/* - * We want to use the whole number here for convenience, but - * we can't us a union, since bit order is implementation-defined and - * we want chanidx on the highest bits: - * - * [ 0 1 2 3 4 5 6 ... 31 ] - * dual part chandir chanidx - */ -struct arc { - u32 idx; -}; - -#define ARC_DUAL_BITOFF (0) -#define ARC_PART_BITOFF (1) -#define ARC_CHANDIR_BITOFF (1 + PARTS_BITS) -#define ARC_CHANIDX_BITOFF (1 + PARTS_BITS + 1) -#define ARC_CHANIDX_BITS (32 - ARC_CHANIDX_BITOFF) - -/* How many arcs can we have for a single channel? - * linearization parts, both directions, and dual */ -#define ARCS_PER_CHANNEL ((size_t)1 << (PARTS_BITS + 1 + 1)) - -static inline void arc_to_parts(struct arc arc, - u32 *chanidx, - int *chandir, - u32 *part, - bool *dual) -{ - if (chanidx) - *chanidx = (arc.idx >> ARC_CHANIDX_BITOFF); - if (chandir) - *chandir = (arc.idx >> ARC_CHANDIR_BITOFF) & 1; - if (part) - *part = (arc.idx >> ARC_PART_BITOFF) & ((1 << PARTS_BITS)-1); - if (dual) - *dual = (arc.idx >> ARC_DUAL_BITOFF) & 1; -} - -static inline struct arc arc_from_parts(u32 chanidx, int chandir, u32 part, bool dual) -{ - struct arc arc; - - assert(part < CHANNEL_PARTS); - assert(chandir == 0 || chandir == 1); - assert(chanidx < (1U << ARC_CHANIDX_BITS)); - arc.idx = ((u32)dual << ARC_DUAL_BITOFF) - | (part << ARC_PART_BITOFF) - | ((u32)chandir << ARC_CHANDIR_BITOFF) - | (chanidx << ARC_CHANIDX_BITOFF); - return arc; -} - -#define MAX(x, y) (((x) > (y)) ? (x) : (y)) -#define MIN(x, y) (((x) < (y)) ? (x) : (y)) - -struct pay_parameters { - /* The gossmap we are using */ - struct gossmap *gossmap; - const struct gossmap_node *source; - const struct gossmap_node *target; - - /* Extra information we intuited about the channels */ - struct chan_extra_map *chan_extra_map; - - /* Optional bitarray of disabled channels. */ - const bitmap *disabled; - - // how much we pay - struct amount_msat amount; - - // channel linearization parameters - double cap_fraction[CHANNEL_PARTS], - cost_fraction[CHANNEL_PARTS]; - - struct amount_msat max_fee; - double min_probability; - double base_probability; - double delay_feefactor; - double base_fee_penalty; - u32 prob_cost_factor; -}; - -/* Representation of the linear MCF network. - * This contains the topology of the extended network (after linearization and - * addition of arc duality). - * This contains also the arc probability and linear fee cost, as well as - * capacity; these quantities remain constant during MCF execution. */ -struct linear_network -{ - u32 *arc_tail_node; - // notice that a tail node is not needed, - // because the tail of arc is the head of dual(arc) - - struct arc *node_adjacency_next_arc; - struct arc *node_adjacency_first_arc; - - // probability and fee cost associated to an arc - s64 *arc_prob_cost, *arc_fee_cost; - s64 *capacity; - - size_t max_num_arcs,max_num_nodes; -}; - -/* This is the structure that keeps track of the network properties while we - * seek for a solution. */ -struct residual_network { - /* residual capacity on arcs */ - s64 *cap; - - /* some combination of prob. cost and fee cost on arcs */ - s64 *cost; - - /* potential function on nodes */ - s64 *potential; -}; - -/* Helper function. - * Given an arc idx, return the dual's idx in the residual network. */ -static struct arc arc_dual(struct arc arc) -{ - arc.idx ^= (1U << ARC_DUAL_BITOFF); - return arc; -} -/* Helper function. */ -static bool arc_is_dual(struct arc arc) -{ - bool dual; - arc_to_parts(arc, NULL, NULL, NULL, &dual); - return dual; -} - -/* Helper function. - * Given an arc of the network (not residual) give me the flow. */ -static s64 get_arc_flow( - const struct residual_network *network, - const struct arc arc) -{ - assert(!arc_is_dual(arc)); - assert(arc_dual(arc).idx < tal_count(network->cap)); - return network->cap[ arc_dual(arc).idx ]; -} - -/* Helper function. - * Given an arc idx, return the node from which this arc emanates in the residual network. */ -static u32 arc_tail(const struct linear_network *linear_network, - const struct arc arc) -{ - assert(arc.idx < tal_count(linear_network->arc_tail_node)); - return linear_network->arc_tail_node[ arc.idx ]; -} -/* Helper function. - * Given an arc idx, return the node that this arc is pointing to in the residual network. */ -static u32 arc_head(const struct linear_network *linear_network, - const struct arc arc) -{ - const struct arc dual = arc_dual(arc); - assert(dual.idx < tal_count(linear_network->arc_tail_node)); - return linear_network->arc_tail_node[dual.idx]; -} - -/* Helper function. - * Given node idx `node`, return the idx of the first arc whose tail is `node`. - * */ -static struct arc node_adjacency_begin( - const struct linear_network * linear_network, - const u32 node) -{ - assert(node < tal_count(linear_network->node_adjacency_first_arc)); - return linear_network->node_adjacency_first_arc[node]; -} - -/* Helper function. - * Is this the end of the adjacency list. */ -static bool node_adjacency_end(const struct arc arc) -{ - return arc.idx == INVALID_INDEX; -} - -/* Helper function. - * Given node idx `node` and `arc`, returns the idx of the next arc whose tail is `node`. */ -static struct arc node_adjacency_next( - const struct linear_network *linear_network, - const struct arc arc) -{ - assert(arc.idx < tal_count(linear_network->node_adjacency_next_arc)); - return linear_network->node_adjacency_next_arc[arc.idx]; -} - -static bool channel_is_available(const struct gossmap_chan *c, int dir, - const struct gossmap *gossmap, - const bitmap *disabled) -{ - if (!gossmap_chan_set(c, dir)) - return false; - - const u32 chan_idx = gossmap_chan_idx(gossmap, c); - return !bitmap_test_bit(disabled, chan_idx * 2 + dir); -} - -// TODO(eduardo): unit test this -/* Split a directed channel into parts with linear cost function. */ -static bool linearize_channel(const struct pay_parameters *params, - const struct gossmap_chan *c, const int dir, - s64 *capacity, s64 *cost) -{ - struct chan_extra_half *extra_half = get_chan_extra_half_by_chan( - params->gossmap, - params->chan_extra_map, - c, - dir); - - if (!extra_half) { - return false; - } - - /* FIXME: this assertion has been reported to be triggered in issue - * #7535. A quick and dirty solution is to comment it and work-around - * this case. But in principle if we do things the right way we should - * not have htlc_total>known_max. The problem is likely to be - * asynchronous way in which reserved htlcs are removed and known_max is - * updated. */ - // assert( - // amount_msat_less_eq(extra_half->htlc_total, extra_half->known_max)); - assert( - amount_msat_less_eq(extra_half->known_min, extra_half->known_max)); - - s64 h = (extra_half->htlc_total.millisatoshis+999)/1000; /* Raw: linearize_channel */ - s64 a = extra_half->known_min.millisatoshis/1000, /* Raw: linearize_channel */ - b = 1 + extra_half->known_max.millisatoshis/1000; /* Raw: linearize_channel */ - - /* If HTLCs add up to more than the known_max it means we have a - * completely wrong knowledge. */ - // assert(ha it doesn't mean automatically that our - * known_min should have been updated, because we reserve this HTLC - * after sendpay behind the scenes it might happen that sendpay failed - * because of insufficient funds we haven't noticed yet. */ - // assert(h<=a); - - /* We reduce this channel capacity because HTLC are reserving liquidity. */ - a -= h; - b -= h; - a = MAX(a,0); - b = MAX(a+1,b); - - /* An extra bound on capacity, here we use it to reduce the flow such - * that it does not exceed htlcmax. */ - s64 cap_on_capacity = - gossmap_chan_htlc_max(c, dir).millisatoshis/1000; /* Raw: linearize_channel */ - - capacity[0]=a; - cost[0]=0; - assert(params->base_probability > 5e-7); - assert(params->base_probability <= 1.0); - const double base_prob_factor = -log(params->base_probability); - - for(size_t i=1;icap_fraction[i]*(b-a), cap_on_capacity); - cap_on_capacity -= capacity[i]; - assert(cap_on_capacity>=0); - - cost[i] = (params->cost_fraction[i]*1.0/(b-a) + base_prob_factor) - *params->amount.millisatoshis /* Raw: linearize_channel */ - *params->prob_cost_factor; - } - return true; -} - -static struct residual_network * -alloc_residual_network(const tal_t *ctx, const size_t max_num_nodes, - const size_t max_num_arcs) -{ - struct residual_network *residual_network = - tal(ctx, struct residual_network); - if (!residual_network) - goto function_fail; - - residual_network->cap = tal_arrz(residual_network, s64, max_num_arcs); - residual_network->cost = tal_arrz(residual_network, s64, max_num_arcs); - residual_network->potential = - tal_arrz(residual_network, s64, max_num_nodes); - - if (!residual_network->cap || !residual_network->cost || - !residual_network->potential) { - goto function_fail; - } - return residual_network; - - function_fail: - return tal_free(residual_network); -} - -static void init_residual_network( - const struct linear_network * linear_network, - struct residual_network* residual_network) -{ - const size_t max_num_arcs = linear_network->max_num_arcs; - const size_t max_num_nodes = linear_network->max_num_nodes; - - for(struct arc arc = {0};arc.idx < max_num_arcs; ++arc.idx) - { - if(arc_is_dual(arc)) - continue; - - struct arc dual = arc_dual(arc); - residual_network->cap[arc.idx]=linear_network->capacity[arc.idx]; - residual_network->cap[dual.idx]=0; - - residual_network->cost[arc.idx]=residual_network->cost[dual.idx]=0; - } - for(u32 i=0;ipotential[i]=0; - } -} - -static void combine_cost_function( - const struct linear_network* linear_network, - struct residual_network *residual_network, - s64 mu) -{ - for(struct arc arc = {0};arc.idx < linear_network->max_num_arcs; ++arc.idx) - { - if(arc_tail(linear_network,arc)==INVALID_INDEX) - continue; - - const s64 pcost = linear_network->arc_prob_cost[arc.idx], - fcost = linear_network->arc_fee_cost[arc.idx]; - - const s64 combined = pcost==INFINITE || fcost==INFINITE ? INFINITE : - mu*fcost + (MU_MAX-1-mu)*pcost; - - residual_network->cost[arc.idx] - = mu==0 ? pcost : - (mu==(MU_MAX-1) ? fcost : combined); - } -} - -static void linear_network_add_adjacenct_arc( - struct linear_network *linear_network, - const u32 node_idx, - const struct arc arc) -{ - assert(arc.idx < tal_count(linear_network->arc_tail_node)); - linear_network->arc_tail_node[arc.idx] = node_idx; - - assert(node_idx < tal_count(linear_network->node_adjacency_first_arc)); - const struct arc first_arc = linear_network->node_adjacency_first_arc[node_idx]; - - assert(arc.idx < tal_count(linear_network->node_adjacency_next_arc)); - linear_network->node_adjacency_next_arc[arc.idx]=first_arc; - - assert(node_idx < tal_count(linear_network->node_adjacency_first_arc)); - linear_network->node_adjacency_first_arc[node_idx]=arc; -} - -/* Get the fee cost associated to this directed channel. - * Cost is expressed as PPM of the payment. - * - * Choose and integer `c_fee` to linearize the following fee function - * - * fee_msat = base_msat + floor(millionths*x_msat / 10^6) - * - * into - * - * fee_microsat = c_fee * x_sat - * - * use `base_fee_penalty` to weight the base fee and `delay_feefactor` to - * weight the CLTV delay. - * */ -static s64 linear_fee_cost( - const struct gossmap_chan *c, - const int dir, - double base_fee_penalty, - double delay_feefactor) -{ - assert(c); - assert(dir==0 || dir==1); - s64 pfee = c->half[dir].proportional_fee, - bfee = c->half[dir].base_fee, - delay = c->half[dir].delay; - - return pfee + bfee* base_fee_penalty+ delay*delay_feefactor; -} - -static struct linear_network * -init_linear_network(const tal_t *ctx, const struct pay_parameters *params, - char **fail) -{ - tal_t *this_ctx = tal(ctx,tal_t); - - struct linear_network * linear_network = tal(ctx, struct linear_network); - if (!linear_network) { - if (fail) - *fail = tal_fmt(ctx, "bad allocation of linear_network"); - goto function_fail; - } - - const size_t max_num_chans = gossmap_max_chan_idx(params->gossmap); - const size_t max_num_arcs = max_num_chans * ARCS_PER_CHANNEL; - const size_t max_num_nodes = gossmap_max_node_idx(params->gossmap); - - linear_network->max_num_arcs = max_num_arcs; - linear_network->max_num_nodes = max_num_nodes; - - linear_network->arc_tail_node = tal_arr(linear_network,u32,max_num_arcs); - if(!linear_network->arc_tail_node) - { - if (fail) - *fail = tal_fmt(ctx, "bad allocation of arc_tail_node"); - goto function_fail; - } - for(size_t i=0;iarc_tail_node);++i) - linear_network->arc_tail_node[i]=INVALID_INDEX; - - linear_network->node_adjacency_next_arc = tal_arr(linear_network,struct arc,max_num_arcs); - if(!linear_network->node_adjacency_next_arc) - { - if (fail) - *fail = tal_fmt(ctx, "bad allocation of node_adjacency_next_arc"); - goto function_fail; - } - for(size_t i=0;inode_adjacency_next_arc);++i) - linear_network->node_adjacency_next_arc[i].idx=INVALID_INDEX; - - linear_network->node_adjacency_first_arc = tal_arr(linear_network,struct arc,max_num_nodes); - if(!linear_network->node_adjacency_first_arc) - { - if (fail) - *fail = tal_fmt(ctx, "bad allocation of node_adjacency_first_arc"); - goto function_fail; - } - for(size_t i=0;inode_adjacency_first_arc);++i) - linear_network->node_adjacency_first_arc[i].idx=INVALID_INDEX; - - linear_network->arc_prob_cost = tal_arr(linear_network,s64,max_num_arcs); - if(!linear_network->arc_prob_cost) - { - if (fail) - *fail = tal_fmt(ctx, "bad allocation of arc_prob_cost"); - goto function_fail; - } - for(size_t i=0;iarc_prob_cost);++i) - linear_network->arc_prob_cost[i]=INFINITE; - - linear_network->arc_fee_cost = tal_arr(linear_network,s64,max_num_arcs); - if(!linear_network->arc_fee_cost) - { - if (fail) - *fail = tal_fmt(ctx, "bad allocation of arc_fee_cost"); - goto function_fail; - } - for(size_t i=0;iarc_fee_cost);++i) - linear_network->arc_fee_cost[i]=INFINITE; - - linear_network->capacity = tal_arrz(linear_network,s64,max_num_arcs); - if(!linear_network->capacity) - { - if (fail) - *fail = tal_fmt(ctx, "bad allocation of capacity"); - goto function_fail; - } - - for(struct gossmap_node *node = gossmap_first_node(params->gossmap); - node; - node=gossmap_next_node(params->gossmap,node)) - { - const u32 node_id = gossmap_node_idx(params->gossmap,node); - - for(size_t j=0;jnum_chans;++j) - { - int half; - const struct gossmap_chan *c = gossmap_nth_chan(params->gossmap, - node, j, &half); - - if (!channel_is_available(c, half, params->gossmap, - params->disabled)) - continue; - - const u32 chan_id = gossmap_chan_idx(params->gossmap, c); - - const struct gossmap_node *next = gossmap_nth_node(params->gossmap, - c,!half); - - const u32 next_id = gossmap_node_idx(params->gossmap,next); - - if(node_id==next_id) - continue; - - // `cost` is the word normally used to denote cost per - // unit of flow in the context of MCF. - s64 prob_cost[CHANNEL_PARTS], capacity[CHANNEL_PARTS]; - - // split this channel direction to obtain the arcs - // that are outgoing to `node` - if (!linearize_channel(params, c, half, - capacity, prob_cost)) { - if(fail) - *fail = - tal_fmt(ctx, "linearize_channel failed"); - goto function_fail; - } - - const s64 fee_cost = linear_fee_cost(c,half, - params->base_fee_penalty, - params->delay_feefactor); - - // let's subscribe the 4 parts of the channel direction - // (c,half), the dual of these guys will be subscribed - // when the `i` hits the `next` node. - for(size_t k=0;kcapacity[arc.idx] = capacity[k]; - linear_network->arc_prob_cost[arc.idx] = prob_cost[k]; - - linear_network->arc_fee_cost[arc.idx] = fee_cost; - - // + the respective dual - struct arc dual = arc_dual(arc); - - linear_network_add_adjacenct_arc(linear_network,next_id,dual); - - linear_network->capacity[dual.idx] = 0; - linear_network->arc_prob_cost[dual.idx] = -prob_cost[k]; - - linear_network->arc_fee_cost[dual.idx] = -fee_cost; - } - } - } - - tal_free(this_ctx); - return linear_network; - - function_fail: - tal_free(this_ctx); - return tal_free(linear_network); -} - -/* Simple queue to traverse the network. */ -struct queue_data -{ - u32 idx; - struct lqueue_link ql; -}; - -// TODO(eduardo): unit test this -/* Finds an admissible path from source to target, traversing arcs in the - * residual network with capacity greater than 0. - * The path is encoded into prev, which contains the idx of the arcs that are - * traversed. */ -static bool -find_admissible_path(const tal_t *ctx, - const struct linear_network *linear_network, - const struct residual_network *residual_network, - const u32 source, const u32 target, struct arc *prev) -{ - tal_t *this_ctx = tal(ctx,tal_t); - - bool target_found = false; - - for(size_t i=0;iidx = source; - lqueue_enqueue(&myqueue,qdata); - - while(!lqueue_empty(&myqueue)) - { - qdata = lqueue_dequeue(&myqueue); - u32 cur = qdata->idx; - - tal_free(qdata); - - if(cur==target) - { - target_found = true; - break; - } - - for(struct arc arc = node_adjacency_begin(linear_network,cur); - !node_adjacency_end(arc); - arc = node_adjacency_next(linear_network,arc)) - { - // check if this arc is traversable - if(residual_network->cap[arc.idx] <= 0) - continue; - - u32 next = arc_head(linear_network,arc); - - assert(next < tal_count(prev)); - - // if that node has been seen previously - if(prev[next].idx!=INVALID_INDEX) - continue; - - prev[next] = arc; - - qdata = tal(this_ctx,struct queue_data); - qdata->idx = next; - lqueue_enqueue(&myqueue,qdata); - } - } - tal_free(this_ctx); - return target_found; -} - -/* Get the max amount of flow one can send from source to target along the path - * encoded in `prev`. */ -static s64 get_augmenting_flow( - const struct linear_network* linear_network, - const struct residual_network *residual_network, - const u32 source, - const u32 target, - const struct arc *prev) -{ - s64 flow = INFINITE; - - u32 cur = target; - while(cur!=source) - { - assert(curcap[arc.idx]); - - // we are traversing in the opposite direction to the flow, - // hence the next node is at the tail of the arc. - cur = arc_tail(linear_network,arc); - } - - assert(flow0); - return flow; -} - -/* Augment a `flow` amount along the path defined by `prev`.*/ -static void augment_flow( - const struct linear_network *linear_network, - struct residual_network *residual_network, - const u32 source, - const u32 target, - const struct arc *prev, - s64 flow) -{ - u32 cur = target; - - while(cur!=source) - { - assert(cur < tal_count(prev)); - const struct arc arc = prev[cur]; - const struct arc dual = arc_dual(arc); - - assert(arc.idx < tal_count(residual_network->cap)); - assert(dual.idx < tal_count(residual_network->cap)); - - residual_network->cap[arc.idx] -= flow; - residual_network->cap[dual.idx] += flow; - - assert(residual_network->cap[arc.idx] >=0 ); - - // we are traversing in the opposite direction to the flow, - // hence the next node is at the tail of the arc. - cur = arc_tail(linear_network,arc); - } -} - - -// TODO(eduardo): unit test this -/* Finds any flow that satisfy the capacity and balance constraints of the - * uncertainty network. For the balance function condition we have: - * balance(source) = - balance(target) = amount - * balance(node) = 0 , for every other node - * Returns an error code if no feasible flow is found. - * - * 13/04/2023 This implementation uses a simple augmenting path approach. - * */ -static bool find_feasible_flow(const tal_t *ctx, - const struct linear_network *linear_network, - struct residual_network *residual_network, - const u32 source, const u32 target, s64 amount, - char **fail) -{ - assert(amount>=0); - tal_t *this_ctx = tal(ctx,tal_t); - - /* path information - * prev: is the id of the arc that lead to the node. */ - struct arc *prev = tal_arr(this_ctx,struct arc,linear_network->max_num_nodes); - if(!prev) - { - if(fail) - *fail = tal_fmt(ctx, "bad allocation of prev"); - goto function_fail; - } - - while(amount>0) - { - // find a path from source to target - if (!find_admissible_path(this_ctx, linear_network, - residual_network, source, target, - prev)) - - { - if(fail) - *fail = tal_fmt(ctx, "find_admissible_path failed"); - goto function_fail; - } - - // traverse the path and see how much flow we can send - s64 delta = get_augmenting_flow(linear_network, - residual_network, - source,target,prev); - - // commit that flow to the path - delta = MIN(amount,delta); - assert(delta>0 && delta<=amount); - - augment_flow(linear_network,residual_network,source,target,prev,delta); - amount -= delta; - } - - tal_free(this_ctx); - return true; - - function_fail: - tal_free(this_ctx); - return false; -} - -// TODO(eduardo): unit test this -/* Similar to `find_admissible_path` but use Dijkstra to optimize the distance - * label. Stops when the target is hit. */ -static bool find_optimal_path(const tal_t *ctx, struct dijkstra *dijkstra, - const struct linear_network *linear_network, - const struct residual_network *residual_network, - const u32 source, const u32 target, - struct arc *prev, char **fail) -{ - tal_t *this_ctx = tal(ctx,tal_t); - bool target_found = false; - - bitmap *visited = tal_arrz(this_ctx, bitmap, - BITMAP_NWORDS(linear_network->max_num_nodes)); - - if(!visited) - { - if(fail) - *fail = tal_fmt(ctx, "bad allocation of visited"); - goto finish; - } - - - for(size_t i=0;icap[arc.idx] <= 0) - continue; - - u32 next = arc_head(linear_network,arc); - - s64 cij = residual_network->cost[arc.idx] - - residual_network->potential[cur] - + residual_network->potential[next]; - - // Dijkstra only works with non-negative weights - assert(cij>=0); - - if(distance[next]<=distance[cur]+cij) - continue; - - dijkstra_update(dijkstra,next,distance[cur]+cij); - prev[next]=arc; - } - } - - if (!target_found && fail) - *fail = tal_fmt(ctx, "no route to destination"); - - finish: - tal_free(this_ctx); - return target_found; -} - -/* Set zero flow in the residual network. */ -static void zero_flow( - const struct linear_network *linear_network, - struct residual_network *residual_network) -{ - for(u32 node=0;nodemax_num_nodes;++node) - { - residual_network->potential[node]=0; - for(struct arc arc=node_adjacency_begin(linear_network,node); - !node_adjacency_end(arc); - arc = node_adjacency_next(linear_network,arc)) - { - if(arc_is_dual(arc))continue; - - struct arc dual = arc_dual(arc); - - residual_network->cap[arc.idx] = linear_network->capacity[arc.idx]; - residual_network->cap[dual.idx] = 0; - } - } -} - -// TODO(eduardo): unit test this -/* Starting from a feasible flow (satisfies the balance and capacity - * constraints), find a solution that minimizes the network->cost function. - * - * TODO(eduardo) The MCF must be called several times until we get a good - * compromise between fees and probabilities. Instead of re-computing the MCF at - * each step, we might use the previous flow result, which is not optimal in the - * current iteration but I might be not too far from the truth. - * It comes to mind to use cycle cancelling. */ -static bool optimize_mcf(const tal_t *ctx, struct dijkstra *dijkstra, - const struct linear_network *linear_network, - struct residual_network *residual_network, - const u32 source, const u32 target, const s64 amount, - char **fail) -{ - assert(amount>=0); - tal_t *this_ctx = tal(ctx,tal_t); - char *errmsg; - - zero_flow(linear_network,residual_network); - struct arc *prev = tal_arr(this_ctx,struct arc,linear_network->max_num_nodes); - - const s64 *const distance = dijkstra_distance_data(dijkstra); - - s64 remaining_amount = amount; - - while(remaining_amount>0) - { - if (!find_optimal_path(this_ctx, dijkstra, linear_network, - residual_network, source, target, prev, - &errmsg)) { - if (fail) - *fail = - tal_fmt(ctx, "find_optimal_path failed: %s", - errmsg); - goto function_fail; - } - - // traverse the path and see how much flow we can send - s64 delta = get_augmenting_flow(linear_network,residual_network,source,target,prev); - - // commit that flow to the path - delta = MIN(remaining_amount,delta); - assert(delta>0 && delta<=remaining_amount); - - augment_flow(linear_network,residual_network,source,target,prev,delta); - remaining_amount -= delta; - - // update potentials - for(u32 n=0;nmax_num_nodes;++n) - { - // see page 323 of Ahuja-Magnanti-Orlin - residual_network->potential[n] -= MIN(distance[target],distance[n]); - - /* Notice: - * if node i is permanently labeled we have - * d_i<=d_t - * which implies - * MIN(d_i,d_t) = d_i - * if node i is temporarily labeled we have - * d_i>=d_t - * which implies - * MIN(d_i,d_t) = d_t - * */ - } - } - tal_free(this_ctx); - return true; - - function_fail: - - tal_free(this_ctx); - return false; -} - -// flow on directed channels -struct chan_flow -{ - s64 half[2]; -}; - -/* Search in the network a path of positive flow until we reach a node with - * positive balance. */ -static u32 find_positive_balance( - const struct gossmap *gossmap, - const bitmap *disabled, - const struct chan_flow *chan_flow, - const u32 start_idx, - const s64 *balance, - - const struct gossmap_chan **prev_chan, - int *prev_dir, - u32 *prev_idx) -{ - u32 final_idx = start_idx; - - /* TODO(eduardo) - * This is guaranteed to halt if there are no directed flow cycles. - * There souldn't be any. In fact if cost is strickly - * positive, then flow cycles do not exist at all in the - * MCF solution. But if cost is allowed to be zero for - * some arcs, then we might have flow cyles in the final - * solution. We must somehow ensure that the MCF - * algorithm does not come up with spurious flow cycles. */ - while(balance[final_idx]<=0) - { - // printf("%s: node = %d\n",__func__,final_idx); - u32 updated_idx=INVALID_INDEX; - struct gossmap_node *cur - = gossmap_node_byidx(gossmap,final_idx); - - for(size_t i=0;inum_chans;++i) - { - int dir; - const struct gossmap_chan *c - = gossmap_nth_chan(gossmap, - cur,i,&dir); - - if (!channel_is_available(c, dir, gossmap, disabled)) - continue; - - const u32 c_idx = gossmap_chan_idx(gossmap,c); - - // follow the flow - if(chan_flow[c_idx].half[dir]>0) - { - const struct gossmap_node *next - = gossmap_nth_node(gossmap,c,!dir); - u32 next_idx = gossmap_node_idx(gossmap,next); - - - prev_dir[next_idx] = dir; - prev_chan[next_idx] = c; - prev_idx[next_idx] = final_idx; - - updated_idx = next_idx; - break; - } - } - - assert(updated_idx!=INVALID_INDEX); - assert(updated_idx!=final_idx); - - final_idx = updated_idx; - } - return final_idx; -} - -struct list_data -{ - struct list_node list; - struct flow *flow_path; -}; - -static inline uint64_t pseudorand_interval(uint64_t a, uint64_t b) -{ - if (a == b) - return b; - assert(b > a); - return a + pseudorand(b - a); -} - -/* Given a flow in the residual network, build a set of payment flows in the - * gossmap that corresponds to this flow. */ -static struct flow ** -get_flow_paths(const tal_t *ctx, const struct gossmap *gossmap, - const bitmap *disabled, - - // chan_extra_map cannot be const because we use it to keep - // track of htlcs and in_flight sats. - struct chan_extra_map *chan_extra_map, - const struct linear_network *linear_network, - const struct residual_network *residual_network, - - // how many msats in excess we paid for not having msat accuracy - // in the MCF solver - struct amount_msat excess, - const double base_probability, - - // error message - char **fail) -{ - tal_t *this_ctx = tal(ctx,tal_t); - struct flow **flows = tal_arr(ctx,struct flow*,0); - - assert(amount_msat_less(excess, AMOUNT_MSAT(1000))); - - const size_t max_num_chans = gossmap_max_chan_idx(gossmap); - struct chan_flow *chan_flow = tal_arrz(this_ctx,struct chan_flow,max_num_chans); - - const size_t max_num_nodes = gossmap_max_node_idx(gossmap); - s64 *balance = tal_arrz(this_ctx,s64,max_num_nodes); - - const struct gossmap_chan **prev_chan - = tal_arr(this_ctx,const struct gossmap_chan *,max_num_nodes); - - - int *prev_dir = tal_arr(this_ctx,int,max_num_nodes); - u32 *prev_idx = tal_arr(this_ctx,u32,max_num_nodes); - - if (!chan_flow || !balance || !prev_chan || !prev_idx || !prev_dir) { - if (fail) - *fail = tal_fmt(ctx, "bad allocation"); - goto function_fail; - } - - // Convert the arc based residual network flow into a flow in the - // directed channel network. - // Compute balance on the nodes. - for(u32 n = 0;n htlc_max) { - /* htlc_min is too big or htlc_max is too small, - * we cannot send `delta` along this route. - * - * FIXME: We try anyways because failing - * channels will be blacklisted downstream. */ - htlc_min = 0; - } - - /* If we divide this route into different flows make it - * random to avoid routing nodes making correlations. */ - if (delta > htlc_max) { - // FIXME: choosing a number in the range - // [htlc_min, htlc_max] or - // [0.5 htlc_max, htlc_max] - // The choice of the fraction was completely - // arbitrary. - delta = pseudorand_interval( - MAX(htlc_min, (htlc_max * 50) / 100), - htlc_max); - } - - struct flow *fp = tal(this_ctx,struct flow); - fp->path = tal_arr(fp,const struct gossmap_chan *,length); - fp->dirs = tal_arr(fp,int,length); - - balance[node_idx] += delta; - balance[final_idx]-= delta; - - // walk backwards, substract flow - for(u32 cur_idx = final_idx; - cur_idx!=node_idx; - cur_idx=prev_idx[cur_idx]) - { - assert(cur_idx!=INVALID_INDEX); - - const int dir = prev_dir[cur_idx]; - const struct gossmap_chan *const c = prev_chan[cur_idx]; - const u32 c_idx = gossmap_chan_idx(gossmap,c); - - length--; - fp->path[length]=c; - fp->dirs[length]=dir; - // notice: fp->path and fp->dirs have the path - // in the correct order. - - chan_flow[c_idx].half[prev_dir[cur_idx]]-=delta; - } - - assert(delta>0); - - // substract the excess of msats for not having msat - // accuracy - struct amount_msat delivered = amount_msat(delta*1000); - if (!amount_msat_sub(&delivered, delivered, excess)) { - if (fail) - *fail = tal_fmt( - ctx, "unable to substract excess"); - goto function_fail; - } - excess = amount_msat(0); - fp->amount = delivered; - - fp->success_prob = - flow_probability(fp, gossmap, chan_extra_map, false) - * pow(base_probability, tal_count(fp->path)); - if (fp->success_prob < 0) { - if (fail) - *fail = - tal_fmt(ctx, "failed to compute " - "flow probability"); - goto function_fail; - } - - // add fp to flows - tal_arr_expand(&flows, fp); - } - } - - /* Establish ownership. */ - for(size_t i=0;i= min_probability; - bool B_prob_pass = B_prob >= min_probability; - - // all bounds are met - if(A_fee_pass && B_fee_pass && A_prob_pass && B_prob_pass) - { - // prefer lower fees - goto fees_or_prob; - } - - // prefer the solution that satisfies both bounds - if(!(A_fee_pass && A_prob_pass) && (B_fee_pass && B_prob_pass)) - { - return false; - } - // prefer the solution that satisfies both bounds - if((A_fee_pass && A_prob_pass) && !(B_fee_pass && B_prob_pass)) - { - return true; - } - - // no solution satisfies both bounds - - // bound on fee is met - if(A_fee_pass && B_fee_pass) - { - // pick the highest prob. - return A_prob > B_prob; - } - - // bound on prob. is met - if(A_prob_pass && B_prob_pass) - { - goto fees_or_prob; - } - - // prefer the solution that satisfies the bound on fees - if(A_fee_pass && !B_fee_pass) - { - return true; - } - if(B_fee_pass && !A_fee_pass) - { - return false; - } - - // none of them satisfy the fee bound - - // prefer the solution that satisfies the bound on prob. - if(A_prob_pass && !B_prob_pass) - { - return true; - } - if(B_prob_pass && !A_prob_pass) - { - return true; - } - - // no bound whatsoever is satisfied - - fees_or_prob: - - // fees are the same, wins the highest prob. - if(amount_msat_eq(A_fee,B_fee)) - { - return A_prob > B_prob; - } - - // go for fees - return amount_msat_less_eq(A_fee,B_fee); -} - -/* Channels that are not in the chan_extra_map should be disabled. */ -static bool check_disabled(const bitmap *disabled, - const struct gossmap *gossmap, - const struct chan_extra_map *chan_extra_map) -{ - assert(disabled); - assert(gossmap); - assert(chan_extra_map); - - if (tal_bytelen(disabled) != - 2 * bitmap_sizeof(gossmap_max_chan_idx(gossmap))) - return false; - - for (struct gossmap_chan *chan = gossmap_first_chan(gossmap); chan; - chan = gossmap_next_chan(gossmap, chan)) { - const u32 chan_idx = gossmap_chan_idx(gossmap, chan); - /* If both directions are disabled anyways, there is no need to - * fetch their information in chan_extra. */ - if (bitmap_test_bit(disabled, chan_idx * 2 + 0) && - bitmap_test_bit(disabled, chan_idx * 2 + 1)) - continue; - - struct short_channel_id scid = gossmap_chan_scid(gossmap, chan); - struct chan_extra *ce = - chan_extra_map_get(chan_extra_map, scid); - if (!ce) - return false; - } - return true; -} - -// TODO(eduardo): choose some default values for the minflow parameters -/* eduardo: I think it should be clear that this module deals with linear - * flows, ie. base fees are not considered. Hence a flow along a path is - * described with a sequence of directed channels and one amount. - * In the `pay_flow` module there are dedicated routes to compute the actual - * amount to be forward on each hop. - * - * TODO(eduardo): notice that we don't pay fees to forward payments with local - * channels and we can tell with absolute certainty the liquidity on them. - * Check that local channels have fee costs = 0 and bounds with certainty (min=max). */ -// TODO(eduardo): we should LOG_DBG the process of finding the MCF while -// adjusting the frugality factor. -struct flow **minflow(const tal_t *ctx, struct gossmap *gossmap, - const struct gossmap_node *source, - const struct gossmap_node *target, - struct chan_extra_map *chan_extra_map, - const bitmap *disabled, struct amount_msat amount, - struct amount_msat max_fee, double min_probability, - double base_probability, - double delay_feefactor, double base_fee_penalty, - u32 prob_cost_factor, char **fail) -{ - tal_t *this_ctx = tal(ctx,tal_t); - char *errmsg; - struct flow **best_flow_paths = NULL; - - struct pay_parameters *params = tal(this_ctx,struct pay_parameters); - struct dijkstra *dijkstra; - - params->gossmap = gossmap; - params->source = source; - params->target = target; - params->chan_extra_map = chan_extra_map; - - params->disabled = disabled; - - if (!check_disabled(disabled, gossmap, chan_extra_map)) { - if (fail) - *fail = tal_fmt(ctx, "Invalid disabled bitmap."); - goto function_fail; - } - - params->amount = amount; - - // template the channel partition into linear arcs - params->cap_fraction[0]=0; - params->cost_fraction[0]=0; - for(size_t i =1;icap_fraction[i]=CHANNEL_PIVOTS[i]-CHANNEL_PIVOTS[i-1]; - params->cost_fraction[i]= - log((1-CHANNEL_PIVOTS[i-1])/(1-CHANNEL_PIVOTS[i])) - /params->cap_fraction[i]; - - // printf("channel part: %ld, fraction: %lf, cost_fraction: %lf\n", - // i,params->cap_fraction[i],params->cost_fraction[i]); - } - - params->max_fee = max_fee; - params->min_probability = min_probability; - params->delay_feefactor = delay_feefactor; - params->base_fee_penalty = base_fee_penalty; - params->prob_cost_factor = prob_cost_factor; - params->base_probability = base_probability; - - // build the uncertainty network with linearization and residual arcs - struct linear_network *linear_network= init_linear_network(this_ctx, params, &errmsg); - if (!linear_network) { - if(fail) - *fail = tal_fmt(ctx, "init_linear_network failed: %s", - errmsg); - goto function_fail; - } - - struct residual_network *residual_network = - alloc_residual_network(this_ctx, linear_network->max_num_nodes, - linear_network->max_num_arcs); - if (!residual_network) { - if (fail) - *fail = tal_fmt( - ctx, "failed to allocate the residual network"); - goto function_fail; - } - - dijkstra = dijkstra_new(this_ctx, gossmap_max_node_idx(params->gossmap)); - - const u32 target_idx = gossmap_node_idx(params->gossmap,target); - const u32 source_idx = gossmap_node_idx(params->gossmap,source); - - init_residual_network(linear_network,residual_network); - - struct amount_msat best_fee; - double best_prob_success; - - /* TODO(eduardo): - * Some MCF algorithms' performance depend on the size of maxflow. If we - * were to work in units of msats we 1. risking overflow when computing - * costs and 2. we risk a performance overhead for no good reason. - * - * Working in units of sats was my first choice, but maybe working in - * units of 10, or 100 sats could be even better. - * - * IDEA: define the size of our precision as some parameter got at - * runtime that depends on the size of the payment and adjust the MCF - * accordingly. - * For example if we are trying to pay 1M sats our precision could be - * set to 1000sat, then channels that had capacity for 3M sats become 3k - * flow units. */ - const u64 pay_amount_msats = params->amount.millisatoshis % 1000; /* Raw: minflow */ - const u64 pay_amount_sats = params->amount.millisatoshis/1000 /* Raw: minflow */ - + (pay_amount_msats ? 1 : 0); - const struct amount_msat excess - = amount_msat(pay_amount_msats ? 1000 - pay_amount_msats : 0); - - if (!find_feasible_flow(this_ctx, linear_network, residual_network, - source_idx, target_idx, pay_amount_sats, - &errmsg)) { - // there is no flow that satisfy the constraints, we stop here - if(fail) - *fail = tal_fmt(ctx, "failed to find a feasible flow: %s", errmsg); - goto function_fail; - } - - // first flow found - best_flow_paths = get_flow_paths( - this_ctx, params->gossmap, params->disabled, params->chan_extra_map, - linear_network, residual_network, excess, params->base_probability, - &errmsg); - if (!best_flow_paths) { - if (fail) - *fail = - tal_fmt(ctx, "get_flow_paths failed: %s", errmsg); - goto function_fail; - } - best_flow_paths = tal_steal(ctx, best_flow_paths); - - best_prob_success = - flowset_probability(this_ctx, best_flow_paths, params->gossmap, - params->chan_extra_map, false, &errmsg) - * pow(params->base_probability, flowset_size(best_flow_paths)); - if (best_prob_success < 0) { - if (fail) - *fail = tal_fmt( - ctx, - "flowset_probability failed on MaxFlow phase: %s", - errmsg); - goto function_fail; - } - if (!flowset_fee(&best_fee, best_flow_paths)) { - if (fail) - *fail = - tal_fmt(ctx, "flowset_fee failed on MaxFlow phase"); - goto function_fail; - } - - // binary search for a value of `mu` that fits our fee and prob. - // constraints. - // mu=0 corresponds to only probabilities - // mu=MU_MAX-1 corresponds to only fee - s64 mu_left = 0, mu_right = MU_MAX; - while(mu_leftgossmap, params->disabled, - params->chan_extra_map, linear_network, - residual_network, excess, params->base_probability, - &errmsg); - if(!flow_paths) - { - // get_flow_paths doesn't fail unless there is a bug. - if (fail) - *fail = - tal_fmt(ctx, "get_flow_paths failed: %s", errmsg); - goto function_fail; - } - - double prob_success = - flowset_probability(this_ctx, flow_paths, params->gossmap, - params->chan_extra_map, false, &errmsg) - * pow(params->base_probability, flowset_size(flow_paths)); - if (prob_success < 0) { - // flowset_probability doesn't fail unless there is a bug. - if (fail) - *fail = - tal_fmt(ctx, "flowset_probability: %s", errmsg); - goto function_fail; - } - - struct amount_msat fee; - if (!flowset_fee(&fee, flow_paths)) { - // flowset_fee doesn't fail unless there is a bug. - if (fail) - *fail = - tal_fmt(ctx, "flowset_fee failed evaluating MinCostFlow candidate"); - goto function_fail; - } - - /* Is this better than the previous one? */ - if(!best_flow_paths || - is_better(params->max_fee,params->min_probability, - fee,prob_success, - best_fee, best_prob_success)) - { - - best_flow_paths = tal_free(best_flow_paths); - best_flow_paths = tal_steal(ctx,flow_paths); - - best_fee = fee; - best_prob_success=prob_success; - flow_paths = NULL; - } - /* I don't like this candidate. */ - else - tal_free(flow_paths); - - if(amount_msat_greater(fee,params->max_fee)) - { - // too expensive - mu_left = mu+1; - - }else if(prob_success < params->min_probability) - { - // too unlikely - mu_right = mu; - }else - { - // with mu constraints are satisfied, now let's optimize - // the fees - mu_left = mu+1; - } - } - - tal_free(this_ctx); - return best_flow_paths; - - function_fail: - tal_free(this_ctx); - return tal_free(best_flow_paths); -} - diff --git a/plugins/renepay/mcf.h b/plugins/renepay/mcf.h deleted file mode 100644 index 88f368ce80b7..000000000000 --- a/plugins/renepay/mcf.h +++ /dev/null @@ -1,67 +0,0 @@ -#ifndef LIGHTNING_PLUGINS_RENEPAY_MCF_H -#define LIGHTNING_PLUGINS_RENEPAY_MCF_H -#include "config.h" -#include -#include -#include - -struct chan_extra_map; - -enum { - RENEPAY_ERR_OK, - // No feasible flow found, either there is not enough known liquidity (or capacity) - // in the channels to complete the payment - RENEPAY_ERR_NOFEASIBLEFLOW, - // There is at least one feasible flow, but the the cheapest solution that we - // found is too expensive, we return the result anyways. - RENEPAY_ERR_NOCHEAPFLOW -}; - - - -/** - * optimal_payment_flow - API for min cost flow function(s). - * @ctx: context to allocate returned flows from - * @gossmap: the gossip map - * @source: the source to start from - * @target: the target to pay - * @chan_extra_map: hashtable of extra per-channel information - * @disabled: NULL, or a bitmap by channel index of channels not to use. - * @amount: the amount we want to reach @target - * - * @max_fee: the maximum allowed in fees - * - * @min_probability: minimum probability accepted - * - * @delay_feefactor converts 1 block delay into msat, as if it were an additional - * fee. So if a CLTV delay on a node is 5 blocks, that's treated as if it - * were a fee of 5 * @delay_feefactor. - * - * @base_fee_penalty: factor to compute additional proportional cost from each - * unit of base fee. So #base_fee_penalty will be added to the effective - * proportional fee for each msat of base fee. - * - * effective_ppm = proportional_fee + base_fee_msat * base_fee_penalty - * - * @prob_cost_factor: factor used to monetize the probability cost. It is - * defined as the number of ppm (parts per million of the total payment) we - * are willing to pay to improve the probability of success by 0.1%. - * - * k_microsat = floor(1000*prob_cost_factor * payment_sat) - * - * this k is used to compute a prob. cost in units of microsats - * - * cost(payment) = - k_microsat * log Prob(payment) - * - * Return a series of subflows which deliver amount to target, or NULL. - */ -struct flow **minflow(const tal_t *ctx, struct gossmap *gossmap, - const struct gossmap_node *source, - const struct gossmap_node *target, - struct chan_extra_map *chan_extra_map, - const bitmap *disabled, struct amount_msat amount, - struct amount_msat max_fee, double min_probability, - double base_probability, - double delay_feefactor, double base_fee_penalty, - u32 prob_cost_factor, char **fail); -#endif /* LIGHTNING_PLUGINS_RENEPAY_MCF_H */ diff --git a/plugins/renepay/mods.c b/plugins/renepay/mods.c index 49b420f66e9b..956bbeee2ec1 100644 --- a/plugins/renepay/mods.c +++ b/plugins/renepay/mods.c @@ -5,12 +5,10 @@ #include #include #include -#include #include #include #include #include -#include #include #include @@ -25,6 +23,14 @@ void *payment_virtual_program[]; /* Advance the payment virtual machine */ struct command_result *payment_continue(struct payment *payment) { + struct command *cmd = payment_command(payment); + + /* Do not advance until we complete those pending RPC calls. + * If each RPC call was an execution thread this line here would wait + * for all threads to arrive to this point. */ + if (payment->pending_rpcs > 0) + return command_still_pending(cmd); + assert(payment->exec_state != INVALID_STATE); void *op = payment_virtual_program[payment->exec_state++]; @@ -321,145 +327,6 @@ static struct command_result *selfpay_cb(struct payment *payment) REGISTER_PAYMENT_MODIFIER(selfpay, selfpay_cb); -/***************************************************************************** - * getmychannels - * - * Calls listpeerchannels to get and updated state of the local channels. - */ - -static void -uncertainty_update_from_listpeerchannels(struct uncertainty *uncertainty, - const struct short_channel_id_dir *scidd, - struct amount_msat max, bool enabled, - const char *buf, const jsmntok_t *chantok) -{ - if (!enabled) - return; - - struct amount_msat capacity, min, gap; - const char *errmsg = json_scan(tmpctx, buf, chantok, "{total_msat:%}", - JSON_SCAN(json_to_msat, &capacity)); - if (errmsg) - goto error; - - if (!uncertainty_add_channel(pay_plugin->uncertainty, scidd->scid, - capacity)) { - errmsg = tal_fmt( - tmpctx, - "Unable to find/add scid=%s in the uncertainty network", - fmt_short_channel_id(tmpctx, scidd->scid)); - goto error; - } - - if (!amount_msat_scale(&gap, capacity, 0.1) || - !amount_msat_sub(&min, max, gap)) - min = AMOUNT_MSAT(0); - - // FIXME this does not include pending HTLC of ongoing payments! - /* Allow a gap between min and max so that we don't use up all of our - * channels' spendable sats and avoid our local error: - * WIRE_TEMPORARY_CHANNEL_FAILURE: Capacity exceeded - HTLC fee: Xsat - * - * */ - if (!uncertainty_set_liquidity(pay_plugin->uncertainty, scidd, min, - max)) { - errmsg = tal_fmt( - tmpctx, - "Unable to set liquidity to channel scidd=%s in the " - "uncertainty network.", - fmt_short_channel_id_dir(tmpctx, scidd)); - goto error; - } - return; - -error: - plugin_log( - pay_plugin->plugin, LOG_UNUSUAL, - "Failed to update local channel %s from listpeerchannels rpc: %s", - fmt_short_channel_id(tmpctx, scidd->scid), - errmsg); -} - -static void gossmod_cb(struct gossmap_localmods *mods, - const struct node_id *self, - const struct node_id *peer, - const struct short_channel_id_dir *scidd, - struct amount_msat htlcmin, - struct amount_msat htlcmax, - struct amount_msat spendable, - struct amount_msat fee_base, - u32 fee_proportional, - u32 cltv_delta, - bool enabled, - const char *buf, - const jsmntok_t *chantok, - struct payment *payment) -{ - struct amount_msat min, max; - - if (scidd->dir == node_id_idx(self, peer)) { - /* local channels can send up to what's spendable */ - min = AMOUNT_MSAT(0); - max = spendable; - } else { - /* remote channels can send up no more than spendable */ - min = htlcmin; - max = amount_msat_min(spendable, htlcmax); - } - - /* FIXME: features? */ - gossmap_local_addchan(mods, self, peer, scidd->scid, NULL); - - gossmap_local_updatechan(mods, scidd->scid, min, max, - fee_base.millisatoshis, /* Raw: gossmap */ - fee_proportional, - cltv_delta, - enabled, - scidd->dir); - - /* Is it disabled? */ - if (!enabled) - payment_disable_chan(payment, *scidd, LOG_DBG, - "listpeerchannels says not enabled"); - - /* Also update the uncertainty network by fixing the liquidity of the - * outgoing channel. If we try to set the liquidity of the incoming - * channel as well we would have conflicting information because our - * knowledge model does not take into account channel reserves. */ - if (scidd->dir == node_id_idx(self, peer)) - uncertainty_update_from_listpeerchannels( - pay_plugin->uncertainty, scidd, max, enabled, buf, chantok); -} - -static struct command_result *getmychannels_done(struct command *cmd, - const char *buf, - const jsmntok_t *result, - struct payment *payment) -{ - // FIXME: should local gossmods be global (ie. member of pay_plugin) or - // local (ie. member of payment)? - payment->local_gossmods = gossmods_from_listpeerchannels( - payment, &pay_plugin->my_id, buf, result, /* zero_rates = */ true, - gossmod_cb, payment); - - return payment_continue(payment); -} - -static struct command_result *getmychannels_cb(struct payment *payment) -{ - struct command *cmd = payment_command(payment); - if (!cmd) - plugin_err(pay_plugin->plugin, - "getmychannels_pay_mod: cannot get a valid cmd."); - - struct out_req *req = jsonrpc_request_start( - cmd->plugin, cmd, "listpeerchannels", getmychannels_done, - payment_rpc_failure, payment); - return send_outreq(cmd->plugin, req); -} - -REGISTER_PAYMENT_MODIFIER(getmychannels, getmychannels_cb); - /***************************************************************************** * refreshgossmap * @@ -470,7 +337,6 @@ static struct command_result *refreshgossmap_cb(struct payment *payment) { assert(pay_plugin->gossmap); // gossmap must be already initialized assert(payment); - assert(payment->local_gossmods); size_t num_channel_updates_rejected = 0; bool gossmap_changed = @@ -481,20 +347,6 @@ static struct command_result *refreshgossmap_cb(struct payment *payment) "gossmap ignored %zu channel updates", num_channel_updates_rejected); - if (gossmap_changed) { - gossmap_apply_localmods(pay_plugin->gossmap, - payment->local_gossmods); - int skipped_count = uncertainty_update(pay_plugin->uncertainty, - pay_plugin->gossmap); - gossmap_remove_localmods(pay_plugin->gossmap, - payment->local_gossmods); - if (skipped_count) - plugin_log( - pay_plugin->plugin, LOG_UNUSUAL, - "%s: uncertainty was updated but %d channels have " - "been ignored.", - __func__, skipped_count); - } return payment_continue(payment); } @@ -506,135 +358,155 @@ REGISTER_PAYMENT_MODIFIER(refreshgossmap, refreshgossmap_cb); * Use route hints from the invoice to update the local gossmods and uncertainty * network. */ -// TODO check how this is done in pay.c -static void add_hintchan(struct payment *payment, const struct node_id *src, - const struct node_id *dst, u16 cltv_expiry_delta, - const struct short_channel_id scid, u32 fee_base_msat, - u32 fee_proportional_millionths) +static struct command_result *askrene_hint_done(struct command *cmd, + const char *buf UNUSED, + const jsmntok_t *result UNUSED, + struct payment *payment) { - assert(payment); - assert(payment->local_gossmods); - - int dir = node_id_idx(src, dst); - - const char *errmsg; - const struct chan_extra *ce = - uncertainty_find_channel(pay_plugin->uncertainty, scid); - - if (!ce) { - /* This channel is not public, we don't know his capacity - One possible solution is set the capacity to - MAX_CAP and the state to [0,MAX_CAP]. Alternatively we could - the capacity to amount and state to [amount,amount], but that - wouldn't work if the recepient provides more than one hints - telling us to partition the payment in multiple routes. */ - ce = uncertainty_add_channel(pay_plugin->uncertainty, scid, - MAX_CAPACITY); - if (!ce) { - errmsg = tal_fmt(tmpctx, - "Unable to find/add scid=%s in the " - "local uncertainty network", - fmt_short_channel_id(tmpctx, scid)); - goto function_error; - } - /* FIXME: features? */ - if (!gossmap_local_addchan(payment->local_gossmods, src, dst, - scid, NULL) || - !gossmap_local_updatechan( - payment->local_gossmods, scid, - /* We assume any HTLC is allowed */ - AMOUNT_MSAT(0), MAX_CAPACITY, fee_base_msat, - fee_proportional_millionths, cltv_expiry_delta, true, - dir)) { - errmsg = tal_fmt( - tmpctx, - "Failed to update scid=%s in the local_gossmods.", - fmt_short_channel_id(tmpctx, scid)); - goto function_error; - } - } else { - /* The channel is pubic and we already keep track of it in the - * gossmap and uncertainty network. It would be wrong to assume - * that this channel has sufficient capacity to forward the - * entire payment! Doing so leads to knowledge updates in which - * the known min liquidity is greater than the channel's - * capacity. */ - } - - return; + assert(payment->pending_rpcs > 0); + payment->pending_rpcs--; + return payment_continue(payment); +} -function_error: - plugin_log(pay_plugin->plugin, LOG_UNUSUAL, - "Failed to update hint channel %s: %s", - fmt_short_channel_id(tmpctx, scid), - errmsg); +static struct command_result *askrene_hint_fail(struct command *cmd, + const char *buf, + const jsmntok_t *result, + struct payment *payment) +{ + plugin_log( + cmd->plugin, LOG_UNUSUAL, + "failed to add channel hint with askrene-create-channel: %.*s", + json_tok_full_len(result), json_tok_full(buf, result)); + return askrene_hint_done(cmd, buf, result, payment); } -static struct command_result *routehints_done(struct command *cmd UNUSED, - const char *buf UNUSED, - const jsmntok_t *result UNUSED, - struct payment *payment) +static struct command_result *routehints_cb(struct payment *payment) { - // FIXME are there route hints for B12? - assert(payment); - assert(payment->local_gossmods); + struct command *cmd = payment_command(payment); + assert(cmd); + + /* For each channel hint, if not in gossmap make a request to + * askrene-create-channel + * */ + size_t num_channel_updates_rejected = 0; + bool gossmap_changed = + gossmap_refresh(pay_plugin->gossmap, &num_channel_updates_rejected); + if (gossmap_changed && num_channel_updates_rejected) + plugin_log(pay_plugin->plugin, LOG_DBG, + "gossmap ignored %zu channel updates", + num_channel_updates_rejected); - const struct node_id *destination = &payment->payment_info.destination; const struct route_info **routehints = payment->payment_info.routehints; - assert(routehints); + if (!routehints) + return payment_continue(payment); + + const struct node_id *destination = &payment->payment_info.destination; const size_t nhints = tal_count(routehints); - /* Hints are added to the local_gossmods. */ + for (size_t i = 0; i < nhints; i++) { /* Each one, presumably, leads to the destination */ const struct route_info *r = routehints[i]; const struct node_id *end = destination; for (int j = tal_count(r) - 1; j >= 0; j--) { - add_hintchan(payment, &r[j].pubkey, end, - r[j].cltv_expiry_delta, - r[j].short_channel_id, r[j].fee_base_msat, + struct gossmap_chan *chan = gossmap_find_chan( + pay_plugin->gossmap, &r[j].short_channel_id); + + if (chan) + /* this channel is public, don't add a hint */ + continue; + + /* FIXME: what if the channel is local? can askrene + * handle trying to add the same channel twice? */ + + struct out_req *req = jsonrpc_request_start( + cmd->plugin, cmd, "askrene-create-channel", + askrene_hint_done, askrene_hint_fail, payment); + + json_add_string(req->js, "layer", + payment->private_layer); + json_add_node_id(req->js, "source", &r[j].pubkey); + json_add_node_id(req->js, "destination", end); + json_add_short_channel_id(req->js, "short_channel_id", + r[j].short_channel_id); + json_add_u32(req->js, "fee_base_msat", + r[j].fee_base_msat); + json_add_u32(req->js, "fee_proportional_millionths", r[j].fee_proportional_millionths); + json_add_u32(req->js, "delay", r[j].cltv_expiry_delta); + + /* we don't have this information, we try to guess */ + json_add_amount_msat(req->js, "htlc_minimum_msat", + AMOUNT_MSAT(0)); + json_add_amount_msat(req->js, "htlc_maximum_msat", + MAX_CAPACITY); + json_add_amount_msat(req->js, "capacity_msat", + MAX_CAPACITY); + + send_outreq(cmd->plugin, req); + payment->pending_rpcs++; + end = &r[j].pubkey; } } - - /* Add hints to the uncertainty network. */ - gossmap_apply_localmods(pay_plugin->gossmap, payment->local_gossmods); - int skipped_count = - uncertainty_update(pay_plugin->uncertainty, pay_plugin->gossmap); - gossmap_remove_localmods(pay_plugin->gossmap, payment->local_gossmods); - if (skipped_count) - plugin_log(pay_plugin->plugin, LOG_UNUSUAL, - "%s: uncertainty was updated but %d channels have " - "been ignored.", - __func__, skipped_count); - return payment_continue(payment); } -static struct command_result *routehints_cb(struct payment *payment) -{ - struct command *cmd = payment_command(payment); - assert(cmd); - struct out_req *req = jsonrpc_request_start( - cmd->plugin, cmd, "waitblockheight", routehints_done, - payment_rpc_failure, payment); - json_add_num(req->js, "blockheight", 0); - return send_outreq(cmd->plugin, req); -} - REGISTER_PAYMENT_MODIFIER(routehints, routehints_cb); /***************************************************************************** * compute_routes * - * Compute the payment routes. + * Get the payment routes calling (askrene)getroutes. */ -static struct command_result *compute_routes_cb(struct payment *payment) +static bool json_to_myroute(const char *buf, const jsmntok_t *tok, + struct route *route) +{ + u64 probability_ppm; + const char *err = + json_scan(tmpctx, buf, tok, "{probability_ppm:%,amount_msat:%}", + JSON_SCAN(json_to_u64, &probability_ppm), + JSON_SCAN(json_to_msat, &route->amount)); + if (err) + return false; + route->success_prob = probability_ppm * 1e-6; + const jsmntok_t *path_tok = json_get_member(buf, tok, "path"); + if (!path_tok || path_tok->type != JSMN_ARRAY) + return false; + + route->hops = tal_arr(route, struct route_hop, path_tok->size); + if (!route->hops) + return false; + + size_t i; + const jsmntok_t *hop_tok; + json_for_each_arr(i, hop_tok, path_tok) + { + struct route_hop *hop = &route->hops[i]; + err = json_scan(tmpctx, buf, hop_tok, + "{short_channel_id:%,direction:%,next_node_id:%" + ",amount_msat:%,delay:%}", + JSON_SCAN(json_to_short_channel_id, &hop->scid), + JSON_SCAN(json_to_int, &hop->direction), + JSON_SCAN(json_to_node_id, &hop->node_id), + JSON_SCAN(json_to_msat, &hop->amount), + JSON_SCAN(json_to_u32, &hop->delay)); + if (err) { + route->hops = tal_free(route->hops); + return false; + } + } + route->amount_sent = route->hops[0].amount; + return true; +} + +static struct command_result *getroutes_done(struct command *cmd UNUSED, + const char *buf, + const jsmntok_t *result, + struct payment *payment) { - assert(payment->status == PAYMENT_PENDING); struct routetracker *routetracker = payment->routetracker; assert(routetracker); @@ -643,7 +515,53 @@ static struct command_result *compute_routes_cb(struct payment *payment) plugin_err(pay_plugin->plugin, "%s: no previously computed routes expected.", __func__); + routetracker->computed_routes = tal_free(routetracker->computed_routes); + + const jsmntok_t *routes_tok = json_get_member(buf, result, "routes"); + assert(routes_tok && routes_tok->type == JSMN_ARRAY); + + routetracker->computed_routes = + tal_arr(routetracker, struct route *, 0); + + size_t i; + const jsmntok_t *r; + json_for_each_arr(i, r, routes_tok) + { + struct route *route = new_route( + routetracker->computed_routes, payment->groupid, + payment->next_partid++, payment->payment_info.payment_hash, + AMOUNT_MSAT(0), AMOUNT_MSAT(0)); + assert(route); + tal_arr_expand(&routetracker->computed_routes, route); + bool success = json_to_myroute(buf, r, route); + if (!success) + plugin_err(pay_plugin->plugin, + "%s: failed to parse route from json: %.*s", + __func__, json_tok_full_len(r), + json_tok_full(buf, r)); + const size_t pathlen = tal_count(route->hops); + if (!amount_msat_eq(route->amount, + route->hops[pathlen - 1].amount)) + plugin_log(pay_plugin->plugin, LOG_UNUSUAL, + "%s: route partid=%" PRIu64 + " delivers %s which is different from what " + "it claims to " + "deliver %s", + __func__, route->key.partid, + fmt_amount_msat( + tmpctx, route->hops[pathlen - 1].amount), + fmt_amount_msat(tmpctx, route->amount)); + /* FIXME: it seems that the route we get in response claims to + * deliver an amount which is different to the amount in the + * last hop. */ + route->amount = route->hops[pathlen - 1].amount; + } + return payment_continue(payment); +} +static struct command_result *compute_routes_cb(struct payment *payment) +{ + assert(payment->status == PAYMENT_PENDING); struct amount_msat feebudget, fees_spent, remaining; /* Total feebudget */ @@ -674,48 +592,29 @@ static struct command_result *compute_routes_cb(struct payment *payment) return payment_continue(payment); } - enum jsonrpc_errcode errcode; - const char *err_msg = NULL; - - gossmap_apply_localmods(pay_plugin->gossmap, payment->local_gossmods); - - /* get_routes returns the answer, we assign it to the computed_routes, - * that's why we need to tal_free the older array. Maybe it would be - * better to pass computed_routes as a reference? */ - routetracker->computed_routes = tal_free(routetracker->computed_routes); - - // TODO: add an algorithm selector here - /* We let this return an unlikely path, as it's better to try once than - * simply refuse. Plus, models are not truth! */ - routetracker->computed_routes = get_routes( - routetracker, - &payment->payment_info, - &pay_plugin->my_id, - &payment->payment_info.destination, - pay_plugin->gossmap, - pay_plugin->uncertainty, - payment->disabledmap, - remaining, - feebudget, - &payment->next_partid, - payment->groupid, - &errcode, - &err_msg); - /* Otherwise the error message remains a child of the routetracker. */ - err_msg = tal_steal(tmpctx, err_msg); - - gossmap_remove_localmods(pay_plugin->gossmap, payment->local_gossmods); - - /* Couldn't feasible route, we stop. */ - if (!routetracker->computed_routes || - tal_count(routetracker->computed_routes) == 0) { - if (err_msg == NULL) - err_msg = tal_fmt( - tmpctx, "get_routes returned NULL error message"); - return payment_fail(payment, errcode, "%s", err_msg); - } - - return payment_continue(payment); + struct command *cmd = payment_command(payment); + assert(cmd); + struct out_req *req = + jsonrpc_request_start(cmd->plugin, cmd, "getroutes", getroutes_done, + payment_rpc_failure, payment); + // FIXME: when multi-destination is needed to fully support blinded path + // FIXME: we could have more than one algorithm to compute routes: + // Minimum Cost Flows or the Max-Expected-Value (recently discussed with + // Rene) or Dijkstra + json_add_node_id(req->js, "source", &pay_plugin->my_id); + json_add_node_id(req->js, "destination", + &payment->payment_info.destination); + json_add_amount_msat(req->js, "amount_msat", remaining); + json_array_start(req->js, "layers"); + // FIXME: put here the layers that we use + json_add_string(req->js, NULL, "auto.sourcefree"); + json_add_string(req->js, NULL, "auto.localchans"); + json_add_string(req->js, NULL, RENEPAY_LAYER); + json_add_string(req->js, NULL, payment->private_layer); + json_array_end(req->js); + json_add_amount_msat(req->js, "maxfee_msat", feebudget); + json_add_u32(req->js, "final_cltv", payment->payment_info.final_cltv); + return send_outreq(cmd->plugin, req); } REGISTER_PAYMENT_MODIFIER(compute_routes, compute_routes_cb); @@ -727,6 +626,57 @@ REGISTER_PAYMENT_MODIFIER(compute_routes, compute_routes_cb); * request calling sendpay. */ +struct sendpay_request { + struct payment *payment; + struct route *route; +}; + +static struct command_result *sendpay_done(struct command *cmd, const char *buf, + const jsmntok_t *result, + struct sendpay_request *data) +{ + struct payment *payment = data->payment; + tal_free(data); + + assert(payment->pending_rpcs > 0); + payment->pending_rpcs--; + return payment_continue(payment); +} + +/* sendpay really only fails immediately in two ways: + * 1. We screwed up and misused the API. + * 2. The first peer is disconnected. + */ +static struct command_result *sendpay_fail(struct command *cmd, const char *buf, + const jsmntok_t *result, + struct sendpay_request *data) +{ + struct payment *payment = data->payment; + struct route *route = data->route; + const char *err = + json_scan(tmpctx, buf, result, "{code:%,message:%}", + JSON_SCAN(json_to_jsonrpc_errcode, &route->final_error), + JSON_SCAN_TAL(route, json_strdup, &route->final_msg)); + if (err) + plugin_err(cmd->plugin, + "Unable to parse sendpay error: %s, json: %.*s", err, + json_tok_full_len(result), + json_tok_full(buf, result)); + + payment_note(payment, LOG_INFORM, + "Sendpay failed: partid=%" PRIu64 + " errorcode:%d message=%s", + route->key.partid, route->final_error, route->final_msg); + + if (route->final_error != PAY_TRY_OTHER_ROUTE) + plugin_log(cmd->plugin, LOG_UNUSUAL, + "Strange error from sendpay: %.*s", + json_tok_full_len(result), + json_tok_full(buf, result)); + + return sendpay_done(cmd, buf, result, data); +} + static struct command_result *send_routes_cb(struct payment *payment) { assert(payment); @@ -744,7 +694,57 @@ static struct command_result *send_routes_cb(struct payment *payment) for (size_t i = 0; i < tal_count(routetracker->computed_routes); i++) { struct route *route = routetracker->computed_routes[i]; - route_sendpay_request(cmd, take(route), payment); + const struct payment_info *pinfo = &payment->payment_info; + + struct sendpay_request *data = tal(cmd, struct sendpay_request); + data->payment = payment; + data->route = route; + + struct out_req *req = + jsonrpc_request_start(cmd->plugin, cmd, "sendpay", + sendpay_done, sendpay_fail, data); + + json_add_route_hops(req->js, "route", route->hops); + json_add_sha256(req->js, "payment_hash", &pinfo->payment_hash); + json_add_u64(req->js, "partid", route->key.partid); + json_add_u64(req->js, "groupid", route->key.groupid); + + /* FIXME: sendpay has a check that we don't total more than + * the exact amount, if we're setting partid (i.e. MPP). + * However, we always set partid, and we add a shadow amount if + * we've only have one part, so we have to use that amount + * here. + * + * The spec was loosened so you are actually allowed + * to overpay, so this check is now overzealous. */ + const size_t pathlen = tal_count(route->hops); + if (pathlen > 0 && + amount_msat_greater(route_delivers(route), pinfo->amount)) + json_add_amount_msat(req->js, "amount_msat", + route_delivers(route)); + else + json_add_amount_msat(req->js, "amount_msat", + pinfo->amount); + + if (pinfo->payment_secret) + json_add_secret(req->js, "payment_secret", + pinfo->payment_secret); + + /* FIXME: some of these fields might not be required for all + * payment parts. */ + json_add_string(req->js, "bolt11", pinfo->invstr); + + if (pinfo->payment_metadata) + json_add_hex_talarr(req->js, "payment_metadata", + pinfo->payment_metadata); + if (pinfo->label) + json_add_string(req->js, "label", pinfo->label); + if (pinfo->description) + json_add_string(req->js, "description", + pinfo->description); + + send_outreq(cmd->plugin, req); + payment->pending_rpcs++; payment_note(payment, LOG_INFORM, "Sent route request: partid=%" PRIu64 @@ -755,12 +755,150 @@ static struct command_result *send_routes_cb(struct payment *payment) fmt_amount_msat(tmpctx, route_fees(route)), route_delay(route), fmt_route_path(tmpctx, route)); } - tal_resize(&routetracker->computed_routes, 0); return payment_continue(payment); } REGISTER_PAYMENT_MODIFIER(send_routes, send_routes_cb); +/***************************************************************************** + * reserve_routes + * + * Reserves liquidity in the uncertainty network corresponding to the routes + * that we send by calling askrene-reserve. + */ + +static struct command_result * +askrene_disable_channel_done(struct command *cmd, const char *buf UNUSED, + const jsmntok_t *result UNUSED, + struct payment *payment); + +static struct command_result * +askrene_disable_channel_fail(struct command *cmd, const char *buf, + const jsmntok_t *result, struct payment *payment); + +struct reserve_request{ + struct payment *payment; + struct route *route; +}; + +static struct command_result * +askrene_reserve_continue(struct reserve_request *r) +{ + struct payment *payment = r->payment; + r = tal_free(r); + assert(payment->pending_rpcs > 0); + payment->pending_rpcs--; + return payment_continue(payment); +} + +static struct command_result * +askrene_reserve_done(struct command *cmd, const char *buf UNUSED, + const jsmntok_t *result UNUSED, struct reserve_request *r) +{ + route_mark_reserved(r->route); + return askrene_reserve_continue(r); +} + +static struct command_result *askrene_reserve_fail(struct command *cmd, + const char *buf, + const jsmntok_t *result, + struct reserve_request *r) +{ + plugin_log(cmd->plugin, LOG_UNUSUAL, + "failed to reserve liquidity with askrene-reserve: %.*s", + json_tok_full_len(result), json_tok_full(buf, result)); + return askrene_reserve_continue(r); +} + +static struct command_result *reserve_routes_cb(struct payment *payment) +{ + assert(payment); + struct routetracker *routetracker = payment->routetracker; + assert(routetracker); + if (!routetracker->computed_routes || + tal_count(routetracker->computed_routes) == 0) { + plugin_log(pay_plugin->plugin, LOG_UNUSUAL, + "%s: there are no new routes, skipping.", __func__); + return payment_continue(payment); + } + struct command *cmd = payment_command(payment); + assert(cmd); + for (size_t i = 0; i < tal_count(routetracker->computed_routes); i++) { + struct route *route = routetracker->computed_routes[i]; + assert(route->hops); + assert(tal_count(route->hops)); + + // FIXME: assuming jsonrpc_errcode == 0 means no error + if (route->final_error) { + + /* Disable the first channel in the route */ + const struct short_channel_id_dir scidd = { + .scid = route->hops[0].scid, + .dir = route->hops[0].direction}; + + plugin_log(cmd->plugin, LOG_DBG, + "route(partid=%" PRIu64 + ") failed at sendpay with error %s, " + "disabling the first " + "channel %s", + route->key.partid, route->final_msg, + fmt_short_channel_id_dir(tmpctx, &scidd)); + + struct out_req *req = jsonrpc_request_start( + cmd->plugin, cmd, "askrene-disable-channel", + askrene_disable_channel_done, + askrene_disable_channel_fail, payment); + + /* This constraint only applies to this payment */ + json_add_string(req->js, "layer", + payment->private_layer); + json_add_short_channel_id(req->js, "short_channel_id", + scidd.scid); + json_add_num(req->js, "direction", scidd.dir); + send_outreq(cmd->plugin, req); + + payment->pending_rpcs++; + tal_free(route); + } else { + // FIXME: don't forget to unreserve, maybe add a + // destructor to the route that calls unreserve + struct reserve_request *r = + tal(NULL, struct reserve_request); + r->payment = payment; + r->route = route; + + struct out_req *req = jsonrpc_request_start( + cmd->plugin, cmd, "askrene-reserve", + askrene_reserve_done, askrene_reserve_fail, r); + + json_array_start(req->js, "path"); + for (size_t j = 0; j < tal_count(route->hops); j++) { + json_object_start(req->js, NULL); + json_add_short_channel_id(req->js, + "short_channel_id", + route->hops[j].scid); + json_add_num(req->js, "direction", + route->hops[j].direction); + json_add_amount_msat(req->js, "amount_msat", + route->hops[j].amount); + json_object_end(req->js); + } + json_array_end(req->js); + + send_outreq(cmd->plugin, req); + + payment->pending_rpcs++; + // TODO: check this + route_pending_register(payment->routetracker, + take(route)); + } + } + tal_resize(&routetracker->computed_routes, 0); + return payment_continue(payment); +} + +REGISTER_PAYMENT_MODIFIER(reserve_routes, reserve_routes_cb); + /***************************************************************************** * sleep * @@ -794,9 +932,6 @@ REGISTER_PAYMENT_MODIFIER(sleep, sleep_cb); static struct command_result *collect_results_cb(struct payment *payment) { assert(payment); - payment->have_results = false; - payment->retry = false; - /* pending sendpay callbacks should be zero */ if (!routetracker_have_results(payment->routetracker)) return payment_continue(payment); @@ -806,7 +941,8 @@ static struct command_result *collect_results_cb(struct payment *payment) enum jsonrpc_errcode final_error = LIGHTNINGD; const char *final_msg = NULL; - payment_collect_results(payment, &payment_preimage, &final_error, &final_msg); + tal_collect_results(tmpctx, payment->routetracker, &payment_preimage, + &final_error, &final_msg); if (payment_preimage) { /* If we have the preimage that means one succeed, we @@ -830,25 +966,6 @@ static struct command_result *collect_results_cb(struct payment *payment) * inmediately finish the payment. */ return payment_fail(payment, final_error, "%s", final_msg); } - - if (amount_msat_greater_eq(payment->total_delivering, - payment->payment_info.amount)) { - /* There are no succeeds but we are still pending delivering the - * entire payment. We still need to collect more results. */ - payment->have_results = false; - payment->retry = false; - } else { - /* We have some failures so that now we are short of - * total_delivering, we may retry. */ - payment->have_results = true; - - // FIXME: we seem to always retry here if we don't fail - // inmediately. But I am going to leave this variable here, - // cause we might decide in the future to put some conditions on - // retries, like a maximum number of retries. - payment->retry = true; - } - return payment_continue(payment); } @@ -1077,19 +1194,36 @@ REGISTER_PAYMENT_MODIFIER(pendingsendpays, pendingsendpays_cb); * Reduce the knowledge of the network as time goes by. */ -static struct command_result *knowledgerelax_cb(struct payment *payment) +static struct command_result *askreneage_success(struct command *cmd UNUSED, + const char *buf UNUSED, + const jsmntok_t *result UNUSED, + struct payment *payment) { - const u64 now_sec = time_now().ts.tv_sec; - enum renepay_errorcode err = uncertainty_relax( - pay_plugin->uncertainty, now_sec - pay_plugin->last_time); - if (err) - plugin_err(pay_plugin->plugin, - "uncertainty_relax failed with error %s", - renepay_errorcode_name(err)); - pay_plugin->last_time = now_sec; return payment_continue(payment); } +/* FIXME: we lack an RPC call to request askrene to relax constraints by a + * smooth amount, instead we will need to forget all knowledge that exceeds a + * certain date. */ +static struct command_result *knowledgerelax_cb(struct payment *payment) +{ + /* Remove all knowledge older than TIMER_FORGET_SEC (number of seconds + * in the past). */ + const u64 cutoff = time_now().ts.tv_sec - TIMER_FORGET_SEC; + struct command *cmd = payment_command(payment); + assert(cmd); + + /* askrene-age may fail if the layer has not yet been created, in that + * case we do nothing. */ + struct out_req *req = jsonrpc_request_start( + cmd->plugin, cmd, "askrene-age", askreneage_success, + askreneage_success, payment); + + json_add_string(req->js, "layer", RENEPAY_LAYER); + json_add_u64(req->js, "cutoff", cutoff); + return send_outreq(cmd->plugin, req); +} + REGISTER_PAYMENT_MODIFIER(knowledgerelax, knowledgerelax_cb); /***************************************************************************** @@ -1110,24 +1244,71 @@ REGISTER_PAYMENT_MODIFIER(knowledgerelax, knowledgerelax_cb); * FIXME: shall we set these threshold parameters as plugin options? */ +static struct command_result * +askrene_disable_channel_done(struct command *cmd, const char *buf UNUSED, + const jsmntok_t *result UNUSED, + struct payment *payment) +{ + assert(payment->pending_rpcs > 0); + payment->pending_rpcs--; + return payment_continue(payment); +} + +static struct command_result * +askrene_disable_channel_fail(struct command *cmd, const char *buf, + const jsmntok_t *result, struct payment *payment) +{ + plugin_log( + cmd->plugin, LOG_UNUSUAL, + "failed to disable channel with askrene-disable-channel: %.*s", + json_tok_full_len(result), json_tok_full(buf, result)); + return askrene_disable_channel_done(cmd, buf, result, payment); +} + +static struct command_result * +askrene_disable_node_done(struct command *cmd, const char *buf UNUSED, + const jsmntok_t *result UNUSED, + struct payment *payment) +{ + assert(payment->pending_rpcs > 0); + payment->pending_rpcs--; + return payment_continue(payment); +} + +static struct command_result *askrene_disable_node_fail(struct command *cmd, + const char *buf, + const jsmntok_t *result, + struct payment *payment) +{ + plugin_log(cmd->plugin, LOG_UNUSUAL, + "failed to disable node with askrene-disable-node: %.*s", + json_tok_full_len(result), json_tok_full(buf, result)); + return askrene_disable_node_done(cmd, buf, result, payment); +} + static struct command_result *channelfilter_cb(struct payment *payment) { assert(payment); + struct command *cmd = payment_command(payment); + assert(cmd); assert(pay_plugin->gossmap); const double HTLC_MAX_FRACTION = 0.01; // 1% - const u64 HTLC_MAX_STOP_MSAT = 1000000000; // 1M sats - - u64 disabled_count = 0; + const struct amount_msat HTLC_MAX_STOP_MSAT = + AMOUNT_MSAT(1000000000); // 1M sats + struct amount_msat htlc_max_threshold; + if (!amount_msat_scale(&htlc_max_threshold, + payment->payment_info.amount, HTLC_MAX_FRACTION)) + plugin_err(cmd->plugin, "%s: error scaling amount_msat", + __func__); - u64 htlc_max_threshold = HTLC_MAX_FRACTION * payment->payment_info - .amount.millisatoshis; /* Raw: a fraction of this amount. */ /* Don't exclude channels with htlc_max above HTLC_MAX_STOP_MSAT even if * that represents a fraction of the payment smaller than * HTLC_MAX_FRACTION. */ - htlc_max_threshold = MIN(htlc_max_threshold, HTLC_MAX_STOP_MSAT); + htlc_max_threshold = + amount_msat_min(htlc_max_threshold, HTLC_MAX_STOP_MSAT); - gossmap_apply_localmods(pay_plugin->gossmap, payment->local_gossmods); + u64 num_disabled = 0; for (const struct gossmap_node *node = gossmap_first_node(pay_plugin->gossmap); node; node = gossmap_next_node(pay_plugin->gossmap, node)) { @@ -1135,30 +1316,89 @@ static struct command_result *channelfilter_cb(struct payment *payment) int dir; const struct gossmap_chan *chan = gossmap_nth_chan( pay_plugin->gossmap, node, i, &dir); - const u64 htlc_max = - fp16_to_u64(chan->half[dir].htlc_max); - if (htlc_max < htlc_max_threshold) { + if (amount_msat_greater_fp16( + htlc_max_threshold, chan->half[dir].htlc_max)) { struct short_channel_id_dir scidd = { .scid = gossmap_chan_scid( pay_plugin->gossmap, chan), .dir = dir}; - disabledmap_add_channel(payment->disabledmap, - scidd); - disabled_count++; + + struct out_req *req = jsonrpc_request_start( + cmd->plugin, cmd, "askrene-disable-channel", + askrene_disable_channel_done, + askrene_disable_channel_fail, payment); + + /* This constraint only applies to this payment + */ + json_add_string(req->js, "layer", + payment->private_layer); + json_add_short_channel_id( + req->js, "short_channel_id", scidd.scid); + json_add_num(req->js, "direction", scidd.dir); + send_outreq(cmd->plugin, req); + + payment->pending_rpcs++; + num_disabled++; } } } - gossmap_remove_localmods(pay_plugin->gossmap, payment->local_gossmods); + // FIXME: prune the network over other parameters, eg. capacity, // fees, ... plugin_log(pay_plugin->plugin, LOG_DBG, "channelfilter: disabling %" PRIu64 " channels.", - disabled_count); + num_disabled); return payment_continue(payment); } REGISTER_PAYMENT_MODIFIER(channelfilter, channelfilter_cb); + +/***************************************************************************** + * manualexclusions + * + * Disable some channels and nodes specified in the command line or during our + * internal workflow. + */ + +static struct command_result *manualexclusions_cb(struct payment *payment) +{ + assert(payment); + struct command *cmd = payment_command(payment); + assert(cmd); + + for(size_t i=0;iexclusions);i++){ + const struct route_exclusion *ex = &payment->exclusions[i]; + if (ex->type == EXCLUDE_CHANNEL) { + struct out_req *req = jsonrpc_request_start( + cmd->plugin, cmd, "askrene-disable-channel", + askrene_disable_channel_done, + askrene_disable_channel_fail, payment); + json_add_string(req->js, "layer", + payment->private_layer); + json_add_short_channel_id(req->js, "short_channel_id", + ex->u.chan_id.scid); + json_add_num(req->js, "direction", ex->u.chan_id.dir); + send_outreq(cmd->plugin, req); + payment->pending_rpcs++; + } else { + struct out_req *req = jsonrpc_request_start( + cmd->plugin, cmd, "askrene-disable-node", + askrene_disable_node_done, + askrene_disable_node_fail, payment); + json_add_string(req->js, "layer", + payment->private_layer); + json_add_node_id(req->js, "node", &ex->u.node_id); + send_outreq(cmd->plugin, req); + payment->pending_rpcs++; + } + } + tal_resize(&payment->exclusions, 0); + return payment_continue(payment); +} + +REGISTER_PAYMENT_MODIFIER(manualexclusions, manualexclusions_cb); + /***************************************************************************** * alwaystrue * @@ -1177,7 +1417,7 @@ REGISTER_PAYMENT_CONDITION(alwaystrue, alwaystrue_cb); */ static bool nothaveresults_cb(const struct payment *payment) { - return !payment->have_results; + return !routetracker_have_results(payment->routetracker); } REGISTER_PAYMENT_CONDITION(nothaveresults, nothaveresults_cb); @@ -1186,8 +1426,10 @@ REGISTER_PAYMENT_CONDITION(nothaveresults, nothaveresults_cb); * retry * * A payment condition that returns true if we should retry the payment. + * + * FIXME: always true? */ -static bool retry_cb(const struct payment *payment) { return payment->retry; } +static bool retry_cb(const struct payment *payment) { return true; } REGISTER_PAYMENT_CONDITION(retry, retry_cb); @@ -1212,23 +1454,23 @@ void *payment_virtual_program[] = { /*0*/ OP_CALL, &previoussuccess_pay_mod, /*2*/ OP_CALL, &selfpay_pay_mod, /*4*/ OP_CALL, &knowledgerelax_pay_mod, - /*6*/ OP_CALL, &getmychannels_pay_mod, - /*8*/ OP_CALL, &refreshgossmap_pay_mod, - /*10*/ OP_CALL, &routehints_pay_mod, - /*12*/OP_CALL, &channelfilter_pay_mod, + /*6*/ OP_CALL, &refreshgossmap_pay_mod, + /*8*/ OP_CALL, &routehints_pay_mod, + /*10*/OP_CALL, &channelfilter_pay_mod, // TODO shadow_additions /* do */ + /*12*/OP_CALL, &manualexclusions_pay_mod, /*14*/ OP_CALL, &pendingsendpays_pay_mod, /*16*/ OP_CALL, &checktimeout_pay_mod, - /*18*/ OP_CALL, &refreshgossmap_pay_mod, - /*20*/ OP_CALL, &compute_routes_pay_mod, - /*22*/ OP_CALL, &send_routes_pay_mod, + /*18*/ OP_CALL, &compute_routes_pay_mod, + /*20*/ OP_CALL, &send_routes_pay_mod, + /*22*/ OP_CALL, &reserve_routes_pay_mod, /*do*/ /*24*/ OP_CALL, &sleep_pay_mod, /*26*/ OP_CALL, &collect_results_pay_mod, /*while*/ /*28*/ OP_IF, ¬haveresults_pay_cond, (void *)24, /* while */ - /*31*/ OP_IF, &retry_pay_cond, (void *)14, + /*31*/ OP_IF, &retry_pay_cond, (void *)12, /*34*/ OP_CALL, &end_pay_mod, /* safety net, default failure if reached */ - /*36*/ NULL}; + /*32*/ NULL}; diff --git a/plugins/renepay/payment.c b/plugins/renepay/payment.c index 58c18f670a2e..0104410bec83 100644 --- a/plugins/renepay/payment.c +++ b/plugins/renepay/payment.c @@ -1,6 +1,7 @@ #include "config.h" #include #include +#include #include #include #include @@ -99,24 +100,29 @@ struct payment *payment_new( /* === Hidden State === */ p->exec_state = INVALID_STATE; + p->pending_rpcs = 0; p->next_partid = 1; p->cmd_array = tal_arr(p, struct command *, 0); - p->local_gossmods = NULL; - p->disabledmap = disabledmap_new(p); - - for (size_t i = 0; i < tal_count(exclusions); i++) { - const struct route_exclusion *ex = exclusions[i]; - if (ex->type == EXCLUDE_CHANNEL) - disabledmap_add_channel(p->disabledmap, ex->u.chan_id); - else - disabledmap_add_node(p->disabledmap, ex->u.node_id); - } - p->have_results = false; - p->retry = false; + p->exclusions = + tal_arr(p, struct route_exclusion, tal_count(exclusions)); + for (size_t i = 0; i < tal_count(p->exclusions); i++) + p->exclusions[i] = *exclusions[i]; + p->waitresult_timer = NULL; p->routetracker = new_routetracker(p, p); + + /* The name of our private layer is just the payment hash in hex. */ + /* FIXME: we need a way to tell askrene to remove obsolete layers */ + const size_t bytelen = sizeof(pinfo->payment_hash); + const size_t hexlen = hex_str_size(bytelen); + p->private_layer = tal_arr(p, char, hexlen); + + if (!hex_encode(&pinfo->payment_hash, bytelen, p->private_layer, + hexlen)) + return tal_free(p); + return p; } @@ -124,16 +130,10 @@ struct payment *payment_new( static void payment_cleanup(struct payment *p) { p->exec_state = INVALID_STATE; + p->pending_rpcs = 0; tal_resize(&p->cmd_array, 0); - p->local_gossmods = tal_free(p->local_gossmods); - - /* FIXME: for optimization, a cleanup should prune all the data that has - * no use after a payent is completed. The entire disablemap structure - * is no longer needed, hence I guess we should free it not just reset - * it. */ - disabledmap_reset(p->disabledmap); + p->exclusions = tal_free(p->exclusions); p->waitresult_timer = tal_free(p->waitresult_timer); - routetracker_cleanup(p->routetracker); } @@ -194,6 +194,7 @@ bool payment_update( /* === Hidden State === */ p->exec_state = INVALID_STATE; + p->pending_rpcs = 0; p->next_partid = 1; /* I shouldn't be calling a payment_update on a payment that has pending @@ -201,21 +202,12 @@ bool payment_update( assert(p->cmd_array); assert(tal_count(p->cmd_array) == 0); - p->local_gossmods = tal_free(p->local_gossmods); + p->exclusions = tal_free(p->exclusions); + p->exclusions = + tal_arr(p, struct route_exclusion, tal_count(exclusions)); + for (size_t i = 0; i < tal_count(p->exclusions); i++) + p->exclusions[i] = *exclusions[i]; - assert(p->disabledmap); - disabledmap_reset(p->disabledmap); - - for (size_t i = 0; i < tal_count(exclusions); i++) { - const struct route_exclusion *ex = exclusions[i]; - if (ex->type == EXCLUDE_CHANNEL) - disabledmap_add_channel(p->disabledmap, ex->u.chan_id); - else - disabledmap_add_node(p->disabledmap, ex->u.node_id); - } - - p->have_results = false; - p->retry = false; p->waitresult_timer = tal_free(p->waitresult_timer); return true; @@ -392,7 +384,11 @@ void payment_disable_chan(struct payment *p, struct short_channel_id_dir scidd, enum log_level lvl, const char *fmt, ...) { assert(p); - assert(p->disabledmap); + assert(p->exclusions); + struct route_exclusion ex; + ex.type = EXCLUDE_CHANNEL; + ex.u.chan_id = scidd; + va_list ap; const char *str; @@ -400,16 +396,14 @@ void payment_disable_chan(struct payment *p, struct short_channel_id_dir scidd, str = tal_vfmt(tmpctx, fmt, ap); va_end(ap); payment_note(p, lvl, "disabling %s: %s", - fmt_short_channel_id_dir(tmpctx, &scidd), - str); - disabledmap_add_channel(p->disabledmap, scidd); + fmt_short_channel_id_dir(tmpctx, &scidd), str); + tal_arr_expand(&p->exclusions, ex); } void payment_warn_chan(struct payment *p, struct short_channel_id_dir scidd, enum log_level lvl, const char *fmt, ...) { assert(p); - assert(p->disabledmap); va_list ap; const char *str; @@ -417,7 +411,10 @@ void payment_warn_chan(struct payment *p, struct short_channel_id_dir scidd, str = tal_vfmt(tmpctx, fmt, ap); va_end(ap); - if (disabledmap_channel_is_warned(p->disabledmap, scidd)) { + /* FIXME: add a data structure to remember metadata for channels and + * nodes, so that we can evaluate those channels that cause trouble + * frequently and disable them. Here we disable them by default. */ + if (true) { payment_disable_chan(p, scidd, lvl, "%s, channel warned twice", str); return; @@ -426,14 +423,17 @@ void payment_warn_chan(struct payment *p, struct short_channel_id_dir scidd, payment_note( p, lvl, "flagged for warning %s: %s, next time it will be disabled", fmt_short_channel_id_dir(tmpctx, &scidd), str); - disabledmap_warn_channel(p->disabledmap, scidd); } void payment_disable_node(struct payment *p, struct node_id node, enum log_level lvl, const char *fmt, ...) { assert(p); - assert(p->disabledmap); + assert(p->exclusions); + struct route_exclusion ex; + ex.type = EXCLUDE_NODE; + ex.u.node_id = node; + va_list ap; const char *str; @@ -443,5 +443,5 @@ void payment_disable_node(struct payment *p, struct node_id node, payment_note(p, lvl, "disabling node %s: %s", fmt_node_id(tmpctx, &node), str); - disabledmap_add_node(p->disabledmap, node); + tal_arr_expand(&p->exclusions, ex); } diff --git a/plugins/renepay/payment.h b/plugins/renepay/payment.h index 37e104127279..7a12866f99b9 100644 --- a/plugins/renepay/payment.h +++ b/plugins/renepay/payment.h @@ -4,7 +4,6 @@ #include #include #include -#include #include enum payment_status { PAYMENT_PENDING, PAYMENT_SUCCESS, PAYMENT_FAIL }; @@ -47,28 +46,26 @@ struct payment { /* Position in the payment virtual machine */ u64 exec_state; + /* Number of pending RPCs responses before we move to the next state. */ + u32 pending_rpcs; + /* Used in get_payflows to set ids to each pay_flow. */ u64 next_partid; /* Running commands that want this payment */ struct command **cmd_array; - /* Localmods to apply to gossip_map for our own use. */ - struct gossmap_localmods *local_gossmods; - - struct disabledmap *disabledmap; - - /* Flag to indicate wether we have collected enough results to make a - * decision on the payment progress. */ - bool have_results; - - /* Flag to indicate wether we would like to retry the payment. */ - bool retry; + /* Here we queue channel and node disabling orders. */ + struct route_exclusion *exclusions; /* Timer we use to wait for results. */ struct plugin_timer *waitresult_timer; struct routetracker *routetracker; + + /* The name of the layer where we put information regarding this + * payment. */ + char *private_layer; }; static inline const struct sha256 payment_hash(const struct payment *p) diff --git a/plugins/renepay/payplugin.h b/plugins/renepay/payplugin.h index a0da496f048d..d2c43b0e1473 100644 --- a/plugins/renepay/payplugin.h +++ b/plugins/renepay/payplugin.h @@ -4,10 +4,8 @@ #include #include #include -#include #include #include -#include // TODO(eduardo): renepaystatus should be similar to paystatus @@ -59,9 +57,6 @@ struct pay_plugin { /* All the struct payment */ struct payment_map *payment_map; - /* Per-channel metadata: some persists between payments */ - struct uncertainty *uncertainty; - /* Pending sendpays. Each pending route has an associated HTLC data in * the uncertainty network. Pending routes are matched against sendpay * notifications. */ diff --git a/plugins/renepay/renepayconfig.h b/plugins/renepay/renepayconfig.h index d1ade031618b..605725bb5739 100644 --- a/plugins/renepay/renepayconfig.h +++ b/plugins/renepay/renepayconfig.h @@ -2,6 +2,8 @@ #define LIGHTNING_PLUGINS_RENEPAY_RENEPAYCONFIG_H #include "config.h" +#define RENEPAY_LAYER "renepay" + #define MAX_NUM_ATTEMPTS 10 /* Knowledge is proportionally decreased with time up to TIMER_FORGET_SEC when @@ -11,4 +13,13 @@ /* Time lapse used to wait for failed sendpays. */ #define COLLECTOR_TIME_WINDOW_MSEC 50 +/* FIXME a hard coded constant to indicate a limit on any channel + capacity. Channels for which the capacity is unknown (because they are not + announced) use this value. It makes sense, because if we don't even know the + channel capacity the liquidity could be anything but it will never be greater + than the global number of msats. + It remains to be checked if this value does not lead to overflow somewhere in + the code. */ +#define MAX_CAPACITY (AMOUNT_MSAT(1000000 * MSAT_PER_BTC)) + #endif /* LIGHTNING_PLUGINS_RENEPAY_RENEPAYCONFIG_H */ diff --git a/plugins/renepay/route.c b/plugins/renepay/route.c index 06ac2526fa03..010b9f51ed6e 100644 --- a/plugins/renepay/route.c +++ b/plugins/renepay/route.c @@ -11,90 +11,19 @@ struct route *new_route(const tal_t *ctx, u32 groupid, route->key.groupid = groupid; route->key.payment_hash = payment_hash; - route->final_error = LIGHTNINGD; + // FIXME: assuming jsonrpc_errcode == 0 means no error + route->final_error = 0; route->final_msg = NULL; route->hops = NULL; route->success_prob = 0.0; route->result = NULL; + route->is_reserved = false; route->amount = amount; route->amount_sent = amount_sent; return route; } -/* Construct a route from a flow. - * - * @ctx: allocator - * @groupid, @partid, @payment_hash: unique identification keys for this route - * @final_cltv: final delay required by the payment - * @gossmap: global gossmap - * @flow: the flow to convert to route */ -struct route *flow_to_route(const tal_t *ctx, - u32 groupid, u32 partid, struct sha256 payment_hash, - u32 final_cltv, struct gossmap *gossmap, - struct flow *flow) -{ - struct route *route = - new_route(ctx, groupid, partid, payment_hash, - AMOUNT_MSAT(0), AMOUNT_MSAT(0)); - - size_t pathlen = tal_count(flow->path); - route->hops = tal_arr(route, struct route_hop, pathlen); - - for (size_t i = 0; i < pathlen; i++) { - struct route_hop *hop = &route->hops[i]; - struct gossmap_node *n; - n = gossmap_nth_node(gossmap, flow->path[i], !flow->dirs[i]); - gossmap_node_get_id(gossmap, n, &hop->node_id); - - hop->scid = gossmap_chan_scid(gossmap, flow->path[i]); - hop->direction = flow->dirs[i]; - } - - /* Calculate cumulative delays (backwards) */ - route->hops[pathlen - 1].delay = final_cltv; - route->hops[pathlen - 1].amount = flow->amount; - - for (int i = (int)pathlen - 2; i >= 0; i--) { - const struct half_chan *h = flow_edge(flow, i + 1); - - route->hops[i].delay = route->hops[i + 1].delay + h->delay; - route->hops[i].amount = route->hops[i + 1].amount; - if (!amount_msat_add_fee(&route->hops[i].amount, h->base_fee, - h->proportional_fee)) - goto function_fail; - } - route->success_prob = flow->success_prob; - route->amount = route->hops[pathlen - 1].amount; - route->amount_sent = route->hops[0].amount; - return route; - -function_fail: - return tal_free(route); -} - -struct route **flows_to_routes(const tal_t *ctx, - u32 groupid, u32 partid, - struct sha256 payment_hash, u32 final_cltv, - struct gossmap *gossmap, struct flow **flows) -{ - assert(gossmap); - assert(flows); - const size_t N = tal_count(flows); - struct route **routes = tal_arr(ctx, struct route *, N); - for (size_t i = 0; i < N; i++) { - routes[i] = - flow_to_route(routes, groupid, partid++, - payment_hash, final_cltv, gossmap, flows[i]); - if (!routes[i]) - goto function_fail; - } - return routes; - -function_fail: - return tal_free(routes); -} - const char *fmt_route_path(const tal_t *ctx, const struct route *route) { tal_t *this_ctx = tal(ctx, tal_t); diff --git a/plugins/renepay/route.h b/plugins/renepay/route.h index 52a80d7c2165..15bc3c4e6cdc 100644 --- a/plugins/renepay/route.h +++ b/plugins/renepay/route.h @@ -11,7 +11,6 @@ #include #include #include -#include #include struct payment; @@ -68,8 +67,20 @@ struct route { /* result of waitsenday */ struct payment_result *result; + + /* Mark if we called askrene-reserve with this route */ + bool is_reserved; }; +static inline bool route_is_reserved(const struct route *r) +{ + return r->is_reserved; +} +static inline void route_mark_reserved(struct route *r) +{ + r->is_reserved = true; +} + static inline struct routekey routekey(const struct sha256 *hash, u64 groupid, u64 partid) { @@ -114,16 +125,6 @@ struct route *new_route(const tal_t *ctx, u32 groupid, struct amount_msat amount, struct amount_msat amount_sent); -struct route *flow_to_route(const tal_t *ctx, - u32 groupid, u32 partid, struct sha256 payment_hash, - u32 final_cltv, struct gossmap *gossmap, - struct flow *flow); - -struct route **flows_to_routes(const tal_t *ctx, - u32 groupid, u32 partid, - struct sha256 payment_hash, u32 final_cltv, - struct gossmap *gossmap, struct flow **flows); - static inline struct short_channel_id_dir hop_to_scidd(const struct route_hop *hop) { diff --git a/plugins/renepay/routebuilder.c b/plugins/renepay/routebuilder.c deleted file mode 100644 index e128a881d64b..000000000000 --- a/plugins/renepay/routebuilder.c +++ /dev/null @@ -1,428 +0,0 @@ -#include "config.h" -#include -#include -#include - -#include - -static void uncertainty_remove_routes(struct uncertainty *uncertainty, - struct route **routes) -{ - const size_t N = tal_count(routes); - for (size_t i = 0; i < N; i++) - uncertainty_remove_htlcs(uncertainty, routes[i]); -} - -/* Shave-off amounts that do not meet the liquidity constraints. Disable - * channels that produce an htlc_max bottleneck. */ -static enum renepay_errorcode -flow_adjust_htlcmax_constraints(struct flow *flow, struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map, - bitmap *disabled_bitmap) -{ - assert(flow); - assert(gossmap); - assert(chan_extra_map); - assert(disabled_bitmap); - assert(!amount_msat_is_zero(flow_delivers(flow))); - - enum renepay_errorcode errorcode; - - struct amount_msat max_deliverable; - const struct gossmap_chan *bad_channel; - - errorcode = flow_maximum_deliverable(&max_deliverable, flow, gossmap, - chan_extra_map, &bad_channel); - - if (!errorcode) { - assert(!amount_msat_is_zero(max_deliverable)); - - // no issues - flow->amount = - amount_msat_min(flow_delivers(flow), max_deliverable); - - return errorcode; - } - - if (errorcode == RENEPAY_BAD_CHANNEL) { - // this is a channel that we can disable - // FIXME: log this error? disabling both directions? - bitmap_set_bit(disabled_bitmap, - gossmap_chan_idx(gossmap, bad_channel) * 2 + 0); - bitmap_set_bit(disabled_bitmap, - gossmap_chan_idx(gossmap, bad_channel) * 2 + 1); - } - - // we had an unexpected error - return errorcode; -} - -static enum renepay_errorcode -route_check_constraints(struct route *route, struct gossmap *gossmap, - struct uncertainty *uncertainty, - bitmap *disabled_bitmap) -{ - assert(route); - assert(route->hops); - const size_t pathlen = tal_count(route->hops); - if (!amount_msat_eq(route->amount, route->hops[pathlen - 1].amount)) - return RENEPAY_PRECONDITION_ERROR; - if (!amount_msat_eq(route->amount_sent, route->hops[0].amount)) - return RENEPAY_PRECONDITION_ERROR; - - for (size_t i = 0; i < pathlen; i++) { - struct route_hop *hop = &route->hops[i]; - int dir = hop->direction; - struct gossmap_chan *chan = - gossmap_find_chan(gossmap, &hop->scid); - assert(chan); - struct chan_extra *ce = - uncertainty_find_channel(uncertainty, hop->scid); - - // check that we stay within the htlc max and min limits - if (amount_msat_greater(hop->amount, - gossmap_chan_htlc_max(chan, dir)) || - amount_msat_less(hop->amount, - gossmap_chan_htlc_min(chan, dir))) { - bitmap_set_bit(disabled_bitmap, - gossmap_chan_idx(gossmap, chan) * 2 + - dir); - return RENEPAY_BAD_CHANNEL; - } - - // check that the sum of all htlcs and this amount does not - // exceed the maximum known by our knowledge - struct amount_msat total_htlcs = ce->half[dir].htlc_total; - if (!amount_msat_add(&total_htlcs, total_htlcs, hop->amount)) - return RENEPAY_AMOUNT_OVERFLOW; - - if (amount_msat_greater(total_htlcs, ce->half[dir].known_max)) - return RENEPAY_UNEXPECTED; - } - return RENEPAY_NOERROR; -} - -static void tal_report_error(const tal_t *ctx, enum jsonrpc_errcode *ecode, - const char **fail, - enum jsonrpc_errcode error_value, const char *fmt, - ...) -{ - tal_t *this_ctx = tal(ctx, tal_t); - - va_list ap; - const char *str; - - va_start(ap, fmt); - str = tal_vfmt(this_ctx, fmt, ap); - va_end(ap); - - if (ecode) - *ecode = error_value; - - if (fail) - *fail = tal_fmt(ctx, "%s", str); - - this_ctx = tal_free(this_ctx); -} - -/* Routes are computed and saved in the payment for later use. */ -struct route **get_routes(const tal_t *ctx, - struct payment_info *payment_info, - - const struct node_id *source, - const struct node_id *destination, - struct gossmap *gossmap, - struct uncertainty *uncertainty, - struct disabledmap *disabledmap, - - struct amount_msat amount_to_deliver, - struct amount_msat feebudget, - - u64 *next_partid, - u64 groupid, - - enum jsonrpc_errcode *ecode, - const char **fail) -{ - assert(gossmap); - assert(uncertainty); - - const tal_t *this_ctx = tal(ctx, tal_t); - struct route **routes = tal_arr(ctx, struct route *, 0); - - double probability_budget = payment_info->min_prob_success; - const double base_probability = payment_info->base_prob_success; - double delay_feefactor = payment_info->delay_feefactor; - const double base_fee_penalty = payment_info->base_fee_penalty; - const double prob_cost_factor = payment_info->prob_cost_factor; - const unsigned int maxdelay = payment_info->maxdelay; - bool delay_feefactor_updated = true; - - bitmap *disabled_bitmap = - tal_disabledmap_get_bitmap(this_ctx, disabledmap, gossmap); - - if (!disabled_bitmap) { - tal_report_error(ctx, ecode, fail, PLUGIN_ERROR, - "Failed to build disabled_bitmap."); - goto function_fail; - } - if (amount_msat_is_zero(amount_to_deliver)) { - tal_report_error(ctx, ecode, fail, PLUGIN_ERROR, - "amount to deliver is zero"); - goto function_fail; - } - - /* Also disable every channel that we don't have in the chan_extra_map. - * We might have channels in the gossmap that are not usable for - * probability computations for example if we don't know their capacity. - * We can tell the solver to ignore those channels by disabling them - * here. - */ - for (struct gossmap_chan *chan = gossmap_first_chan(gossmap); chan; - chan = gossmap_next_chan(gossmap, chan)) { - const u32 chan_id = gossmap_chan_idx(gossmap, chan); - struct short_channel_id scid = gossmap_chan_scid(gossmap, chan); - struct chan_extra *ce = - chan_extra_map_get(uncertainty->chan_extra_map, scid); - if (!ce) { - bitmap_set_bit(disabled_bitmap, chan_id * 2 + 0); - bitmap_set_bit(disabled_bitmap, chan_id * 2 + 1); - } - } - - const struct gossmap_node *src, *dst; - src = gossmap_find_node(gossmap, source); - if (!src) { - tal_report_error(ctx, ecode, fail, PAY_ROUTE_NOT_FOUND, - "We don't have any channels."); - goto function_fail; - } - dst = gossmap_find_node(gossmap, destination); - if (!dst) { - tal_report_error( - ctx, ecode, fail, PAY_ROUTE_NOT_FOUND, - "Destination is unknown in the network gossip."); - goto function_fail; - } - - char *errmsg; - - while (!amount_msat_is_zero(amount_to_deliver)) { - - /* TODO: choose an algorithm, could be something like - * payment->algorithm, that we set up based on command line - * options and that can be changed according to some conditions - * met during the payment process, eg. add "select_solver" pay - * mod. */ - /* TODO: use uncertainty instead of chan_extra */ - - /* Min. Cost Flow algorithm to find optimal flows. */ - struct flow **flows = - minflow(this_ctx, gossmap, src, dst, - uncertainty_get_chan_extra_map(uncertainty), - disabled_bitmap, amount_to_deliver, feebudget, - probability_budget, - base_probability, - delay_feefactor, - base_fee_penalty, - prob_cost_factor, &errmsg); - delay_feefactor_updated = false; - - if (!flows) { - tal_report_error( - ctx, ecode, fail, PAY_ROUTE_NOT_FOUND, - "minflow couldn't find a feasible flow: %s", - errmsg); - goto function_fail; - } - - enum renepay_errorcode errorcode; - for (size_t i = 0; i < tal_count(flows); i++) { - - // do we overpay? - if (amount_msat_greater(flows[i]->amount, - amount_to_deliver)) { - // should not happen - tal_report_error( - ctx, ecode, fail, PLUGIN_ERROR, - "%s: flow is delivering to destination " - "(%s) more than requested (%s)", - __func__, - fmt_amount_msat(this_ctx, flows[i]->amount), - fmt_amount_msat(this_ctx, - amount_to_deliver)); - goto function_fail; - } - - // fees considered, remove the least amount as to fit in - // with the htlcmax constraints - errorcode = flow_adjust_htlcmax_constraints( - flows[i], gossmap, - uncertainty_get_chan_extra_map(uncertainty), - disabled_bitmap); - if (errorcode == RENEPAY_BAD_CHANNEL) - // we handle a bad channel error by disabling - // it, infinite loops are avoided since we have - // everytime less and less channels - continue; - if (errorcode) { - // any other error is bad - tal_report_error( - ctx, ecode, fail, PLUGIN_ERROR, - "flow_adjust_htlcmax_constraints returned " - "errorcode: %s", - renepay_errorcode_name(errorcode)); - goto function_fail; - } - - // a bound check, we shouldn't deliver a zero amount, it - // would mean a bug somewhere - if (amount_msat_is_zero(flows[i]->amount)) { - tal_report_error(ctx, ecode, fail, PLUGIN_ERROR, - "flow conveys a zero amount"); - goto function_fail; - } - - const double prob = flow_probability( - flows[i], gossmap, - uncertainty_get_chan_extra_map(uncertainty), true); - if (prob < 0) { - // should not happen - tal_report_error(ctx, ecode, fail, PLUGIN_ERROR, - "flow_probability failed"); - goto function_fail; - } - - // this flow seems good, build me a route - struct route *r = flow_to_route( - this_ctx, groupid, *next_partid, - payment_info->payment_hash, - payment_info->final_cltv, gossmap, flows[i]); - - if (!r) { - tal_report_error( - ctx, ecode, fail, PLUGIN_ERROR, - "%s failed to build route from flow.", - __func__); - goto function_fail; - } - - const struct amount_msat fee = route_fees(r); - const struct amount_msat delivering = route_delivers(r); - - // are we still within the fee budget? - if (amount_msat_greater(fee, feebudget)) { - tal_report_error( - ctx, ecode, fail, PAY_ROUTE_TOO_EXPENSIVE, - "Fee exceeds our fee budget, fee=%s " - "(feebudget=%s)", - fmt_amount_msat(this_ctx, fee), - fmt_amount_msat(this_ctx, feebudget)); - goto function_fail; - } - - // check the CLTV delay does not exceed our settings - const unsigned int delay = route_delay(r); - if (delay > maxdelay) { - if (!delay_feefactor_updated) { - delay_feefactor *= 2; - delay_feefactor_updated = true; - } - - /* FIXME: What is a sane limit? */ - if (delay_feefactor > 1000) { - tal_report_error( - ctx, ecode, fail, - PAY_ROUTE_TOO_EXPENSIVE, - "CLTV delay exceeds our CLTV " - "budget, delay=%u (maxdelay=%u)", - delay, maxdelay); - goto function_fail; - } - continue; - } - - // check that the route satisfy all constraints - errorcode = route_check_constraints( - r, gossmap, uncertainty, disabled_bitmap); - - if (errorcode == RENEPAY_BAD_CHANNEL) - continue; - if (errorcode) { - // any other error is bad - tal_report_error( - ctx, ecode, fail, PLUGIN_ERROR, - "route_check_constraints returned " - "errorcode: %s", - renepay_errorcode_name(errorcode)); - goto function_fail; - } - - // update the fee budget - if (!amount_msat_sub(&feebudget, feebudget, fee)) { - // should never happen - tal_report_error( - ctx, ecode, fail, PLUGIN_ERROR, - "%s routing fees (%s) exceed fee " - "budget (%s).", - __func__, - fmt_amount_msat(this_ctx, fee), - fmt_amount_msat(this_ctx, feebudget)); - goto function_fail; - } - - // update the amount that we deliver - if (!amount_msat_sub(&amount_to_deliver, - amount_to_deliver, delivering)) { - // should never happen - tal_report_error( - ctx, ecode, fail, PLUGIN_ERROR, - "%s: route delivering to destination (%s) " - "is more than requested (%s)", - __func__, - fmt_amount_msat(this_ctx, delivering), - fmt_amount_msat(this_ctx, - amount_to_deliver)); - goto function_fail; - } - - // update the probability target - if (prob < 1e-10) { - // probability is too small for division - probability_budget = 1.0; - } else { - /* prob here is a conditional probability, the - * next flow will have a conditional - * probability prob2 and we would like that - * prob*prob2 >= probability_budget hence - * probability_budget/prob becomes the next - * iteration's target. */ - probability_budget = - MIN(1.0, probability_budget / prob); - } - - // route added - (*next_partid)++; - uncertainty_commit_htlcs(uncertainty, r); - tal_arr_expand(&routes, r); - } - } - - /* remove the temporary routes from the uncertainty network */ - uncertainty_remove_routes(uncertainty, routes); - - /* ownership */ - for (size_t i = 0; i < tal_count(routes); i++) - routes[i] = tal_steal(routes, routes[i]); - - tal_free(this_ctx); - return routes; - -function_fail: - /* remove the temporary routes from the uncertainty network */ - uncertainty_remove_routes(uncertainty, routes); - - /* Discard any routes we have constructed here. */ - tal_free(this_ctx); - return tal_free(routes); -} diff --git a/plugins/renepay/routebuilder.h b/plugins/renepay/routebuilder.h deleted file mode 100644 index 2692d2a82adc..000000000000 --- a/plugins/renepay/routebuilder.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef LIGHTNING_PLUGINS_RENEPAY_ROUTEBUILDER_H -#define LIGHTNING_PLUGINS_RENEPAY_ROUTEBUILDER_H - -#include "config.h" -#include -#include -#include -#include -#include -#include - -struct route **get_routes(const tal_t *ctx, - struct payment_info *payment_info, - - const struct node_id *source, - const struct node_id *destination, - struct gossmap *gossmap, - struct uncertainty *uncertainty, - struct disabledmap *disabledmap, - - struct amount_msat amount_to_deliver, - struct amount_msat feebudget, - - u64 *next_partid, - u64 groupid, - - enum jsonrpc_errcode *ecode, - const char **fail); - -#endif /* LIGHTNING_PLUGINS_RENEPAY_ROUTEBUILDER_H */ diff --git a/plugins/renepay/routefail.c b/plugins/renepay/routefail.c index 175b7d5b03d4..8ebc513eef78 100644 --- a/plugins/renepay/routefail.c +++ b/plugins/renepay/routefail.c @@ -3,7 +3,6 @@ #include #include #include -#include #include enum node_type { @@ -13,45 +12,21 @@ enum node_type { UNKNOWN_NODE }; -struct routefail { - struct command *cmd; - struct payment *payment; - struct route *route; -}; - -static struct command_result *update_gossip(struct routefail *r); -static struct command_result *handle_failure(struct routefail *r); +static struct command_result * +routefail_update_gossip(struct route_notification *r); +static struct command_result * +routefail_handle_cases(struct route_notification *r); +static struct command_result * +routefail_update_knowledge(struct route_notification *r); -struct command_result *routefail_start(const tal_t *ctx, struct route *route, - struct command *cmd) +struct command_result *routefail_start(struct route_notification *r) { - assert(route); - struct routefail *r = tal(ctx, struct routefail); - struct payment *payment = - payment_map_get(pay_plugin->payment_map, route->key.payment_hash); - - if (payment == NULL) - plugin_err(pay_plugin->plugin, - "%s: payment with hash %s not found.", - __func__, - fmt_sha256(tmpctx, &route->key.payment_hash)); - - r->payment = payment; - r->route = route; - r->cmd = cmd; - assert(route->result); - return update_gossip(r); + return routefail_update_gossip(r); } -static struct command_result *routefail_end(struct routefail *r TAKES) +static struct command_result *routefail_end(struct route_notification *r) { - /* Notify the tracker that route has failed and routefail have completed - * handling all possible errors cases. */ - struct command *cmd = r->cmd; - route_failure_register(r->payment->routetracker, r->route); - if (taken(r)) - r = tal_steal(tmpctx, r); - return notification_handled(cmd); + return route_unreserve(r); } /***************************************************************************** @@ -112,15 +87,15 @@ static u8 *channel_update_from_onion_error(const tal_t *ctx, static struct command_result *update_gossip_done(struct command *cmd UNUSED, const char *buf UNUSED, const jsmntok_t *result UNUSED, - struct routefail *r) + struct route_notification *r) { - return handle_failure(r); + return routefail_handle_cases(r); } static struct command_result *update_gossip_failure(struct command *cmd UNUSED, const char *buf, const jsmntok_t *result, - struct routefail *r) + struct route_notification *r) { assert(r); assert(r->payment); @@ -139,7 +114,8 @@ static struct command_result *update_gossip_failure(struct command *cmd UNUSED, return update_gossip_done(cmd, buf, result, r); } -static struct command_result *update_gossip(struct routefail *r) +static struct command_result * +routefail_update_gossip(struct route_notification *r) { /* if there is no raw_message we continue */ if (!r->route->result->raw_message) @@ -158,7 +134,7 @@ static struct command_result *update_gossip(struct routefail *r) return send_outreq(r->cmd->plugin, req); skip_update_gossip: - return handle_failure(r); + return routefail_handle_cases(r); } /***************************************************************************** @@ -186,7 +162,8 @@ static void route_final_error(struct route *route, enum jsonrpc_errcode error, route->final_msg = tal_strdup(route, what); } -static struct command_result *handle_failure(struct routefail *r) +static struct command_result * +routefail_handle_cases(struct route_notification *r) { /* BOLT #4: * @@ -437,5 +414,86 @@ static struct command_result *handle_failure(struct routefail *r) break; } - return routefail_end(take(r)); + return routefail_update_knowledge(r); +} + +/***************************************************************************** + * update_knowledge + * + * Make RPC calls to askrene to pass information about the liquidity constraint + * we have learned from the errors. + */ + +static struct command_result * +askrene_inform_done(struct command *cmd UNUSED, const char *buf UNUSED, + const jsmntok_t *result UNUSED, + struct route_notification *r) +{ + return routefail_end(r); +} + +static struct command_result * +askrene_inform_fail(struct command *cmd UNUSED, const char *buf UNUSED, + const jsmntok_t *result UNUSED, + struct route_notification *r) +{ + plugin_log( + cmd->plugin, LOG_UNUSUAL, + "%s: failed RPC call to askrene-inform-channel, returned: %.*s", + __func__, json_tok_full_len(result), json_tok_full(buf, result)); + return askrene_inform_done(cmd, buf, result, r); +} + +static struct command_result * +routefail_update_knowledge(struct route_notification *r) +{ + /* If we don't have the hops we can't learn anything. */ + if (!r->route->hops || !r->route->result + || !r->route->result->erring_index) { + plugin_log(r->cmd->plugin, LOG_DBG, + "Cannot update knowledge from route %s, missing " + "hops or result.", + fmt_routekey(tmpctx, &r->route->key)); + goto skip_update_knowledge; + } + + const int path_len = tal_count(r->route->hops); + const int last_good_channel = *r->route->result->erring_index - 1; + + if (last_good_channel >= path_len) + plugin_err(r->cmd->plugin, + "%s: last_good_channel (%d) >= path_len (%d)", + __func__, last_good_channel, path_len); + + if(r->route->result->failcode!=WIRE_TEMPORARY_CHANNEL_FAILURE || + (last_good_channel + 1)>=path_len) + goto skip_update_knowledge; + + /* A WIRE_TEMPORARY_CHANNEL_FAILURE could mean not enough liquidity to + * forward the payment or cannot add one more HTLC. */ + const struct short_channel_id scid = + r->route->hops[last_good_channel + 1].scid; + const int direction = r->route->hops[last_good_channel + 1].direction; + const struct amount_msat this_amt = + r->route->hops[last_good_channel + 1].amount; + + // FIXME: call askrene-query-reserve + // if this route was reseved then + // call askrene-inform-channel for amt + // else + // call askrene-inform-channel for amt+this_amnt + + struct out_req *req = jsonrpc_request_start( + r->cmd->plugin, r->cmd, "askrene-inform-channel", + askrene_inform_done, askrene_inform_fail, r); + + json_add_string(req->js, "layer", RENEPAY_LAYER); + json_add_short_channel_id(req->js, "short_channel_id", scid); + json_add_num(req->js, "direction", direction); + json_add_amount_msat(req->js, "maximum_msat", this_amt); + + return send_outreq(r->cmd->plugin, req); + +skip_update_knowledge: + return routefail_end(r); } diff --git a/plugins/renepay/routefail.h b/plugins/renepay/routefail.h index bad3aabef283..213764170cab 100644 --- a/plugins/renepay/routefail.h +++ b/plugins/renepay/routefail.h @@ -4,9 +4,8 @@ /* This module provides the state machine for handling route failures. */ #include "config.h" -#include +#include -struct command_result *routefail_start(const tal_t *ctx, struct route *route, - struct command *cmd); +struct command_result *routefail_start(struct route_notification *r); #endif /* LIGHTNING_PLUGINS_RENEPAY_ROUTEFAIL_H */ diff --git a/plugins/renepay/routetracker.c b/plugins/renepay/routetracker.c index f08f8becbd78..c9608e708af1 100644 --- a/plugins/renepay/routetracker.c +++ b/plugins/renepay/routetracker.c @@ -45,15 +45,19 @@ void routetracker_cleanup(struct routetracker *routetracker) // TODO } -static void routetracker_add_to_final(struct routetracker *routetracker, - struct route *route) +static struct command_result * +routetracker_add_to_final(struct route_notification *r) { + struct payment *payment = r->payment; + struct command *cmd = r->cmd; + struct route *route = r->route; + struct routetracker *routetracker = payment->routetracker; + + r = tal_free(r); + tal_arr_expand(&routetracker->finalized_routes, route); tal_steal(routetracker->finalized_routes, route); - struct payment *payment = - payment_map_get(pay_plugin->payment_map, route->key.payment_hash); - assert(payment); if (payment->exec_state == INVALID_STATE) { /* payment is offline, collect results now and set the payment * state accordingly. */ @@ -65,83 +69,29 @@ static void routetracker_add_to_final(struct routetracker *routetracker, enum jsonrpc_errcode final_error = LIGHTNINGD; const char *final_msg = NULL; - /* Finalized routes must be processed and removed in order to - * free the uncertainty network's HTLCs. */ - payment_collect_results(payment, &payment_preimage, - &final_error, &final_msg); + tal_collect_results(tmpctx, payment->routetracker, + &payment_preimage, &final_error, + &final_msg); if (payment_preimage) { /* If we have the preimage that means one succeed, we * inmediately finish the payment. */ register_payment_success(payment, take(payment_preimage)); - return; } - if (final_msg) { + else if (final_msg) { /* We received a sendpay result with a final error * message, we inmediately finish the payment. */ register_payment_fail(payment, final_error, "%s", final_msg); - return; } } -} - -static void route_success_register(struct routetracker *routetracker, - struct route *route) -{ - if(route->hops){ - uncertainty_route_success(pay_plugin->uncertainty, route); - } - routetracker_add_to_final(routetracker, route); -} -void route_failure_register(struct routetracker *routetracker, - struct route *route) -{ - struct payment_result *result = route->result; - assert(result); - - /* Update the knowledge in the uncertaity network. */ - if (route->hops) { - assert(result->erring_index); - int path_len = tal_count(route->hops); - - /* index of the last channel before the erring node */ - const int last_good_channel = *result->erring_index - 1; - - if (last_good_channel >= path_len) { - plugin_err(pay_plugin->plugin, - "last_good_channel (%d) >= path_len (%d)", - last_good_channel, path_len); - } - - /* All channels before the erring node could forward the - * payment. */ - for (int i = 0; i <= last_good_channel; i++) { - uncertainty_channel_can_send(pay_plugin->uncertainty, - route->hops[i].scid, - route->hops[i].direction); - } - - if (result->failcode == WIRE_TEMPORARY_CHANNEL_FAILURE && - (last_good_channel + 1) < path_len) { - /* A WIRE_TEMPORARY_CHANNEL_FAILURE could mean not - * enough liquidity to forward the payment or cannot add - * one more HTLC. - */ - uncertainty_channel_cannot_send( - pay_plugin->uncertainty, - route->hops[last_good_channel + 1].scid, - route->hops[last_good_channel + 1].direction); - } - } - routetracker_add_to_final(routetracker, route); + return notification_handled(cmd); } static void remove_route(struct route *route, struct route_map *map) { route_map_del(map, route); - uncertainty_remove_htlcs(pay_plugin->uncertainty, route); } /* This route is pending, ie. locked in HTLCs. @@ -149,8 +99,8 @@ static void remove_route(struct route *route, struct route_map *map) * - after a sendpay is accepted, * - or after listsendpays reveals some pending route that we didn't * previously know about. */ -static void route_pending_register(struct routetracker *routetracker, - struct route *route) +void route_pending_register(struct routetracker *routetracker, + struct route *route) { assert(route); assert(routetracker); @@ -172,8 +122,6 @@ static void route_pending_register(struct routetracker *routetracker, __func__, fmt_routekey(tmpctx, &route->key)); - uncertainty_commit_htlcs(pay_plugin->uncertainty, route); - if (!tal_steal(pay_plugin->pending_routes, route) || !route_map_add(pay_plugin->pending_routes, route) || !tal_add_destructor2(route, remove_route, @@ -192,78 +140,11 @@ static void route_pending_register(struct routetracker *routetracker, } } -/* Callback function for sendpay request success. */ -static struct command_result *sendpay_done(struct command *cmd, - const char *buf UNUSED, - const jsmntok_t *result UNUSED, - struct route *route) +void tal_collect_results(const tal_t *ctx, struct routetracker *routetracker, + struct preimage **payment_preimage, + enum jsonrpc_errcode *final_error, + const char **final_msg) { - assert(route); - struct payment *payment = route_get_payment_verify(route); - route_pending_register(payment->routetracker, route); - return command_still_pending(cmd); -} - -/* sendpay really only fails immediately in two ways: - * 1. We screwed up and misused the API. - * 2. The first peer is disconnected. - */ -static struct command_result *sendpay_failed(struct command *cmd, - const char *buf, - const jsmntok_t *tok, - struct route *route) -{ - assert(route); - struct payment *payment = route_get_payment_verify(route); - struct routetracker *routetracker = payment->routetracker; - assert(routetracker); - - enum jsonrpc_errcode errcode; - const char *msg; - const char *err; - - err = json_scan(tmpctx, buf, tok, "{code:%,message:%}", - JSON_SCAN(json_to_jsonrpc_errcode, &errcode), - JSON_SCAN_TAL(tmpctx, json_strdup, &msg)); - if (err) - plugin_err(pay_plugin->plugin, - "Unable to parse sendpay error: %s, json: %.*s", err, - json_tok_full_len(tok), json_tok_full(buf, tok)); - - payment_note(payment, LOG_INFORM, - "Sendpay failed: partid=%" PRIu64 - " errorcode:%d message=%s", - route->key.partid, errcode, msg); - - if (errcode != PAY_TRY_OTHER_ROUTE) { - plugin_log(pay_plugin->plugin, LOG_UNUSUAL, - "Strange error from sendpay: %.*s", - json_tok_full_len(tok), json_tok_full(buf, tok)); - } - - /* There is no new knowledge from this kind of failure. - * We just disable this scid. */ - struct short_channel_id_dir scidd_disable = { - .scid = route->hops[0].scid, .dir = route->hops[0].direction}; - payment_disable_chan(payment, scidd_disable, LOG_INFORM, - "sendpay didn't like first hop: %s", msg); - - if (!route_map_del(routetracker->sent_routes, route)) - plugin_err(pay_plugin->plugin, - "%s: route (%s) is not marked as sent", - __func__, - fmt_routekey(tmpctx, &route->key)); - tal_free(route); - return command_still_pending(cmd); -} - -void payment_collect_results(struct payment *payment, - struct preimage **payment_preimage, - enum jsonrpc_errcode *final_error, - const char **final_msg) -{ - assert(payment); - struct routetracker *routetracker = payment->routetracker; assert(routetracker); const size_t ncompleted = tal_count(routetracker->finalized_routes); for (size_t i = 0; i < ncompleted; i++) { @@ -278,45 +159,17 @@ void payment_collect_results(struct payment *payment, if (r->result->status == SENDPAY_COMPLETE && payment_preimage) { assert(r->result->payment_preimage); *payment_preimage = - tal_dup(tmpctx, struct preimage, + tal_dup(ctx, struct preimage, r->result->payment_preimage); break; } - /* We should never start a new groupid while there are pending - * onions with a different groupid. We ignore any failure that - * does not have the same groupid as the one we used for our - * routes. */ - if (payment->groupid != r->key.groupid) { - plugin_log(pay_plugin->plugin, LOG_UNUSUAL, - "%s: current groupid=%" PRIu64 - ", but recieved a sendpay result with " - "groupid=%" PRIu64, - __func__, payment->groupid, - r->key.groupid); - continue; - } - - assert(r->result->status == SENDPAY_FAILED && - payment->groupid == r->key.groupid); - if (r->final_msg) { if (final_error) *final_error = r->final_error; if (final_msg) - *final_msg = tal_strdup(tmpctx, r->final_msg); - } - - if (!amount_msat_sub(&payment->total_delivering, - payment->total_delivering, - route_delivers(r)) || - !amount_msat_sub(&payment->total_sent, payment->total_sent, - route_sends(r))) { - plugin_err(pay_plugin->plugin, - "%s: routes do not add up to " - "payment total amount.", - __func__); + *final_msg = tal_strdup(ctx, r->final_msg); } } for (size_t i = 0; i < ncompleted; i++) @@ -324,20 +177,42 @@ void payment_collect_results(struct payment *payment, tal_resize(&routetracker->finalized_routes, 0); } -struct command_result *route_sendpay_request(struct command *cmd, - struct route *route TAKES, - struct payment *payment) +static struct command_result * +askrene_unreserve_done(struct command *cmd, const char *buf, + const jsmntok_t *tok, struct route_notification *r) { - struct out_req *req = - jsonrpc_request_start(pay_plugin->plugin, cmd, "sendpay", - sendpay_done, sendpay_failed, route); - - json_add_route(req->js, route, payment); + return routetracker_add_to_final(r); +} +static struct command_result * +askrene_unreserve_fail(struct command *cmd, const char *buf, + const jsmntok_t *tok, struct route_notification *r) +{ + /* FIXME: we should implement a safer way to add and remove reserves. */ + plugin_log(cmd->plugin, LOG_UNUSUAL, "askrene-unreserve failed: %.*s", + json_tok_full_len(tok), json_tok_full(buf, tok)); + return askrene_unreserve_done(cmd, buf, tok, r); +} - route_map_add(payment->routetracker->sent_routes, route); - if(taken(route)) - tal_steal(payment->routetracker->sent_routes, route); - return send_outreq(pay_plugin->plugin, req); +struct command_result *route_unreserve(struct route_notification *r) +{ + if (!route_is_reserved(r->route)) + return routetracker_add_to_final(r); + + struct out_req *req = jsonrpc_request_start( + r->cmd->plugin, r->cmd, "askrene-unreserve", askrene_unreserve_done, + askrene_unreserve_fail, r); + json_array_start(req->js, "path"); + for (size_t i = 0; i < tal_count(r->route->hops); i++) { + json_object_start(req->js, NULL); + json_add_short_channel_id(req->js, "short_channel_id", + r->route->hops[i].scid); + json_add_num(req->js, "direction", r->route->hops[i].direction); + json_add_amount_msat(req->js, "amount_msat", + r->route->hops[i].amount); + json_object_end(req->js); + } + json_array_end(req->js); + return send_outreq(r->cmd->plugin, req); } struct command_result *notification_sendpay_failure(struct command *cmd, @@ -402,9 +277,12 @@ struct command_result *notification_sendpay_failure(struct command *cmd, route->result->status = SENDPAY_FAILED; } - /* we do some error processing steps before calling - * route_failure_register. */ - return routefail_start(payment, route, cmd); + /* we do some error processing steps before calling */ + struct route_notification *r = tal(NULL, struct route_notification); + r->cmd = cmd; + r->payment = payment; + r->route = route; + return routefail_start(r); } struct command_result *notification_sendpay_success(struct command *cmd, @@ -432,8 +310,6 @@ struct command_result *notification_sendpay_success(struct command *cmd, return notification_handled(cmd); } - struct routetracker *routetracker = payment->routetracker; - assert(routetracker); struct route *route = route_map_get(pay_plugin->pending_routes, key); if (!route) { @@ -454,6 +330,10 @@ struct command_result *notification_sendpay_success(struct command *cmd, json_tok_full_len(sub), json_tok_full(buf, sub)); assert(route->result->status == SENDPAY_COMPLETE); - route_success_register(payment->routetracker, route); - return notification_handled(cmd); + + struct route_notification *r = tal(NULL, struct route_notification); + r->cmd = cmd; + r->payment = payment; + r->route = route; + return route_unreserve(r); } diff --git a/plugins/renepay/routetracker.h b/plugins/renepay/routetracker.h index 2e4c440a2339..efe8e25b401a 100644 --- a/plugins/renepay/routetracker.h +++ b/plugins/renepay/routetracker.h @@ -19,6 +19,15 @@ struct routetracker{ struct route **finalized_routes; }; +/* Data used to make RPC calls after a sendpay notification was received. */ +struct route_notification { + struct command *cmd; + struct payment *payment; + struct route *route; +}; + +struct command_result *route_unreserve(struct route_notification *r); + struct routetracker *new_routetracker(const tal_t *ctx, struct payment *payment); void routetracker_cleanup(struct routetracker *routetracker); @@ -29,15 +38,13 @@ bool routetracker_have_results(struct routetracker *routetracker); * - update the commited amounts, * - update the uncertainty network, * - and free the allocated memory. */ -void payment_collect_results(struct payment *payment, - struct preimage **payment_preimage, - enum jsonrpc_errcode *final_error, - const char **final_msg); +void tal_collect_results(const tal_t *ctx, struct routetracker *routetracker, + struct preimage **payment_preimage, + enum jsonrpc_errcode *final_error, + const char **final_msg); -/* Sends a sendpay request for this route. */ -struct command_result *route_sendpay_request(struct command *cmd, - struct route *route TAKES, - struct payment *payment); +void route_pending_register(struct routetracker *routetracker, + struct route *route); struct command_result *notification_sendpay_failure(struct command *cmd, const char *buf, @@ -47,13 +54,4 @@ struct command_result *notification_sendpay_success(struct command *cmd, const char *buf, const jsmntok_t *params); -/* Notify the tracker that this route has failed. */ -void route_failure_register(struct routetracker *routetracker, - struct route *route); - -// FIXME: double-check that we actually get one notification for each sendpay, -// ie. that after some time we don't have yet pending sendpays for old failed or -// successful payments that we havent processed because we haven't received the -// notification - #endif /* LIGHTNING_PLUGINS_RENEPAY_ROUTETRACKER_H */ diff --git a/plugins/renepay/test/Makefile b/plugins/renepay/test/Makefile index 2cf525d60d8e..a80a0d0b6d67 100644 --- a/plugins/renepay/test/Makefile +++ b/plugins/renepay/test/Makefile @@ -9,8 +9,6 @@ ALL_TEST_PROGRAMS += $(PLUGIN_RENEPAY_TEST_PROGRAMS) $(PLUGIN_RENEPAY_TEST_OBJS): $(PLUGIN_RENEPAY_SRC) PLUGIN_RENEPAY_TEST_COMMON_OBJS := \ - plugins/renepay/dijkstra.o \ - plugins/renepay/chan_extra.o \ bitcoin/chainparams.o \ common/gossmap.o \ common/fp16.o \ diff --git a/plugins/renepay/test/run-arc.c b/plugins/renepay/test/run-arc.c deleted file mode 100644 index 793a0e70a0e2..000000000000 --- a/plugins/renepay/test/run-arc.c +++ /dev/null @@ -1,86 +0,0 @@ -#include "config.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../flow.c" -#include "../mcf.c" - -/* AUTOGENERATED MOCKS START */ -/* Generated stub for sciddir_or_pubkey_from_node_id */ -bool sciddir_or_pubkey_from_node_id(struct sciddir_or_pubkey *sciddpk UNNEEDED, - const struct node_id *node_id UNNEEDED) -{ fprintf(stderr, "sciddir_or_pubkey_from_node_id called!\n"); abort(); } -/* AUTOGENERATED MOCKS END */ - -int main(int argc, char *argv[]) -{ - bool dual; - u32 part; - int chandir; - u32 chanidx; - - common_setup(argv[0]); - - for (int i = 0; i < 32; i++) { - for (int j = 0; j < 32; j++) { - for (int k = 0; k < 32; k++) { - struct arc a, a2; - - a.idx = (1U << i) | (1U << j) | (1U << k); - arc_to_parts(a, &chanidx, &chandir, &part, &dual); - a2 = arc_from_parts(chanidx, chandir, part, dual); - assert(a.idx == a2.idx); - } - } - } - - /* Test all chanidx */ - for (int i = 0; i < (1U << ARC_CHANIDX_BITS); i++) { - struct arc a = arc_from_parts(i, chandir, part, dual); - - arc_to_parts(a, &chanidx, NULL, NULL, NULL); - assert(chanidx == i); - } - - /* Test both chandir */ - for (int i = 0; i < 2; i++) { - struct arc a = arc_from_parts(chanidx, i, part, dual); - - arc_to_parts(a, NULL, &chandir, NULL, NULL); - assert(chandir == i); - } - - /* Test all parts */ - for (int i = 0; i < CHANNEL_PARTS; i++) { - struct arc a = arc_from_parts(chanidx, chandir, i, dual); - - arc_to_parts(a, NULL, NULL, &part, NULL); - assert(part == i); - } - - /* Test both dual */ - for (int i = 0; i < 2; i++) { - struct arc a = arc_from_parts(chanidx, chandir, part, i); - - arc_to_parts(a, NULL, NULL, NULL, &dual); - assert(dual == i); - - assert(arc_is_dual(a) == dual); - - a = arc_dual(a); - arc_to_parts(a, NULL, NULL, NULL, &dual); - assert(dual == !i); - assert(arc_is_dual(a) == dual); - } - - common_shutdown(); -} - diff --git a/plugins/renepay/test/run-bottleneck.c b/plugins/renepay/test/run-bottleneck.c deleted file mode 100644 index 510ddebde5e7..000000000000 --- a/plugins/renepay/test/run-bottleneck.c +++ /dev/null @@ -1,286 +0,0 @@ -/* 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" -#include "../flow.c" -#include "../mcf.c" -#include "../uncertainty.c" -#include "../disabledmap.c" -#include "../route.c" -#include "../routebuilder.c" -#include "common.h" - -#include -#include -#include -#include -#include -#include - -/* AUTOGENERATED MOCKS START */ -/* Generated stub for sciddir_or_pubkey_from_node_id */ -bool sciddir_or_pubkey_from_node_id(struct sciddir_or_pubkey *sciddpk UNNEEDED, - const struct node_id *node_id UNNEEDED) -{ fprintf(stderr, "sciddir_or_pubkey_from_node_id called!\n"); abort(); } -/* AUTOGENERATED MOCKS END */ - -static u8 empty_map[] = {10}; - -static const char *print_flows(const tal_t *ctx, const char *desc, - const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map, - struct flow **flows) -{ - tal_t *this_ctx = tal(ctx, tal_t); - double tot_prob = - flowset_probability(tmpctx, flows, gossmap, chan_extra_map, false, NULL); - assert(tot_prob >= 0); - char *buff = tal_fmt(ctx, "%s: %zu subflows, prob %2lf\n", desc, - tal_count(flows), tot_prob); - for (size_t i = 0; i < tal_count(flows); i++) { - struct amount_msat fee, delivered; - tal_append_fmt(&buff, " "); - for (size_t j = 0; j < tal_count(flows[i]->path); j++) { - struct short_channel_id scid = - gossmap_chan_scid(gossmap, flows[i]->path[j]); - tal_append_fmt(&buff, "%s%s", j ? "->" : "", - fmt_short_channel_id(this_ctx, scid)); - } - delivered = flows[i]->amount; - if (!flow_fee(&fee, flows[i])) { - abort(); - } - tal_append_fmt(&buff, " prob %.2f, %s delivered with fee %s\n", - flows[i]->success_prob, - fmt_amount_msat(this_ctx, delivered), - fmt_amount_msat(this_ctx, fee)); - } - - tal_free(this_ctx); - return buff; -} - -#define NUM_NODES 8 - -static void remove_file(char *fname) { assert(!remove(fname)); } - -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-bottleneck.XXXXXX", &gossfile); - tal_add_destructor(gossfile, remove_file); - 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 8, forcing a payment split between - * two routes 1->2->4->5->6->8 and 1->3->4->5->7->8. - * To force the split the total payment amount will be greater than the - * channel 1-2 and 1-3 capacities. Then channel 4--5 will be a common - * edge in the payment routes. - * - * MCF does not handle fees hence if the capacity of 4--5 is enough to - * let the entire payment pass, we expect that minflow computes two - * routes that are scaled down by get_route algorithm - * to fit for the fee constraints. - * - * +--2--+ +--6--+ - * | | | | - * 1 4---5 8 - * | | | | - * +--3--+ +--7--+ - * - * */ - 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(60 * 1000 * 1000), - 0, 0, 5, - 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), - 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), - 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), - true); - - assert(mk_short_channel_id(&scid, 4, 5, 0)); - add_connection(fd, &nodes[3], &nodes[4], scid, - AMOUNT_MSAT(0), - /* MCF cuts off at 95% of the conditional capacity, for - * cap = 106k that means only 100.7k sats can be sent - * through this channel. */ - AMOUNT_MSAT(106 * 1000 * 1000), - 0, 0, 5, - 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), - 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), - 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), - 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), - true); - - assert(gossmap_refresh(gossmap, NULL)); - struct uncertainty *uncertainty = uncertainty_new(tmpctx); - int skipped_count = - uncertainty_update(uncertainty, gossmap); - assert(skipped_count==0); - - bitmap *disabled = tal_arrz( - tmpctx, bitmap, 2 * BITMAP_NWORDS(gossmap_max_chan_idx(gossmap))); - assert(disabled); - - char *errmsg; - struct flow **flows; - flows = - minflow(tmpctx, gossmap, gossmap_find_node(gossmap, &nodes[0]), - gossmap_find_node(gossmap, &nodes[7]), - uncertainty->chan_extra_map, disabled, - /* Half the capacity */ - AMOUNT_MSAT(100 * 1000 * 1000), - /* max_fee = */ AMOUNT_MSAT(20 * 1000 * 1000), - /* min probability = */ 0.9, - /* base probability = */ 1.0, - /* delay fee factor = */ 1e-6, - /* base fee penalty */ 10, - /* prob cost factor = */ 10, &errmsg); - - if (!flows) { - printf("Minflow has failed with: %s\n", errmsg); - assert(flows); - } - - if(flows) - printf("%s\n", print_flows(tmpctx, "Simple minflow", gossmap, - uncertainty->chan_extra_map, flows)); - - struct preimage preimage; - - struct amount_msat maxfee = AMOUNT_MSAT(20*1000*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[7]; - 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.base_prob_success = 1.0; - pinfo.use_shadow = false; - - randombytes_buf(&preimage, sizeof(preimage)); - sha256(&pinfo.payment_hash, &preimage, sizeof(preimage)); - - // char hex_preimage[600], hex_sha256[600]; - // assert(hex_encode(preimage.r, sizeof(preimage.r), hex_preimage, sizeof(hex_preimage))); - // assert(hex_encode(pinfo.payment_hash.u.u8, sizeof(pinfo.payment_hash), hex_sha256, sizeof(hex_sha256))); - // printf("preimage: %s\npayment_hash: %s\n", hex_preimage, hex_sha256); - - 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[7], - /* gossmap */gossmap, - /* uncertainty */uncertainty, - disabledmap, - /* amount */ pinfo.amount, - /* feebudget */maxfee, - &next_partid, - groupid, - &errcode, - &err_msg); - - if (!routes) { - printf("get_route failed with error %d: %s\n", errcode, err_msg); - assert(routes); - } - if(routes) - printf("get_routes: %s\n", print_routes(tmpctx, routes)); - - common_shutdown(); -} diff --git a/plugins/renepay/test/run-dijkstra.c b/plugins/renepay/test/run-dijkstra.c deleted file mode 100644 index 49e7a364f53b..000000000000 --- a/plugins/renepay/test/run-dijkstra.c +++ /dev/null @@ -1,100 +0,0 @@ -#include "config.h" -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -/* AUTOGENERATED MOCKS START */ -/* Generated stub for sciddir_or_pubkey_from_node_id */ -bool sciddir_or_pubkey_from_node_id(struct sciddir_or_pubkey *sciddpk UNNEEDED, - const struct node_id *node_id UNNEEDED) -{ fprintf(stderr, "sciddir_or_pubkey_from_node_id called!\n"); abort(); } -/* AUTOGENERATED MOCKS END */ - -static void insertion_in_increasing_distance(const tal_t *ctx) -{ - struct dijkstra *dijkstra = dijkstra_new(ctx,10); - - for(int i=0;i -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* AUTOGENERATED MOCKS START */ -/* Generated stub for disabledmap_add_channel */ -void disabledmap_add_channel(struct disabledmap *p UNNEEDED, - struct short_channel_id_dir scidd UNNEEDED) -{ fprintf(stderr, "disabledmap_add_channel called!\n"); abort(); } -/* Generated stub for disabledmap_add_node */ -void disabledmap_add_node(struct disabledmap *p UNNEEDED, struct node_id node UNNEEDED) -{ fprintf(stderr, "disabledmap_add_node called!\n"); abort(); } -/* Generated stub for disabledmap_channel_is_warned */ -bool disabledmap_channel_is_warned(struct disabledmap *p UNNEEDED, - struct short_channel_id_dir scidd UNNEEDED) -{ fprintf(stderr, "disabledmap_channel_is_warned called!\n"); abort(); } -/* Generated stub for disabledmap_new */ -struct disabledmap *disabledmap_new(const tal_t *ctx UNNEEDED) -{ fprintf(stderr, "disabledmap_new called!\n"); abort(); } -/* Generated stub for disabledmap_reset */ -void disabledmap_reset(struct disabledmap *p UNNEEDED) -{ fprintf(stderr, "disabledmap_reset called!\n"); abort(); } -/* Generated stub for disabledmap_warn_channel */ -void disabledmap_warn_channel(struct disabledmap *p UNNEEDED, - struct short_channel_id_dir scidd UNNEEDED) -{ fprintf(stderr, "disabledmap_warn_channel called!\n"); abort(); } -/* Generated stub for json_add_payment */ -void json_add_payment(struct json_stream *s UNNEEDED, const struct payment *payment UNNEEDED) -{ fprintf(stderr, "json_add_payment called!\n"); abort(); } -/* Generated stub for new_routetracker */ -struct routetracker *new_routetracker(const tal_t *ctx UNNEEDED, struct payment *payment UNNEEDED) -{ fprintf(stderr, "new_routetracker called!\n"); abort(); } -/* Generated stub for pay_plugin */ -struct pay_plugin *pay_plugin; -/* Generated stub for routetracker_cleanup */ -void routetracker_cleanup(struct routetracker *routetracker UNNEEDED) -{ fprintf(stderr, "routetracker_cleanup called!\n"); abort(); } -/* Generated stub for sciddir_or_pubkey_from_node_id */ -bool sciddir_or_pubkey_from_node_id(struct sciddir_or_pubkey *sciddpk UNNEEDED, - const struct node_id *node_id UNNEEDED) -{ fprintf(stderr, "sciddir_or_pubkey_from_node_id called!\n"); abort(); } -/* AUTOGENERATED MOCKS END */ - -static u8 empty_map[] = { - 0 -}; - -static const char* print_flows( - const tal_t *ctx, - const char *desc, - const struct gossmap *gossmap, - struct chan_extra_map* chan_extra_map, - struct flow **flows) -{ - tal_t *this_ctx = tal(ctx,tal_t); - double tot_prob = - flowset_probability(tmpctx, flows, gossmap, chan_extra_map, false, NULL); - assert(tot_prob>=0); - char *buff = tal_fmt(ctx,"%s: %zu subflows, prob %2lf\n", desc, tal_count(flows),tot_prob); - for (size_t i = 0; i < tal_count(flows); i++) { - struct amount_msat fee, delivered; - tal_append_fmt(&buff," "); - for (size_t j = 0; j < tal_count(flows[i]->path); j++) { - struct short_channel_id scid - = gossmap_chan_scid(gossmap, - flows[i]->path[j]); - tal_append_fmt(&buff,"%s%s", j ? "->" : "", - fmt_short_channel_id(this_ctx, scid)); - } - delivered = flows[i]->amount; - if (!flow_fee(&fee, flows[i])) - { - abort(); - } - tal_append_fmt(&buff," prob %.2f, %s delivered with fee %s\n", - flows[i]->success_prob, - fmt_amount_msat(this_ctx, delivered), - fmt_amount_msat(this_ctx, fee)); - } - - tal_free(this_ctx); - return buff; -} - -static void remove_file(char *fname) { assert(!remove(fname)); } - -int main(int argc, char *argv[]) -{ - int fd; - char *gossfile; - struct gossmap *gossmap; - struct node_id l1, l2, l3, l4; - struct short_channel_id scid12, scid13, scid24, scid34; - struct gossmap_localmods *mods; - struct chan_extra_map *chan_extra_map; - - char *errmsg; - - common_setup(argv[0]); - - fd = tmpdir_mkstemp(tmpctx, "run-not_mcf-diamond.XXXXXX", &gossfile); - tal_add_destructor(gossfile, remove_file); - assert(write_all(fd, empty_map, sizeof(empty_map))); - - gossmap = gossmap_load(tmpctx, gossfile, NULL); - assert(gossmap); - - /* These are in ascending order, for easy direction setting */ - assert(node_id_from_hexstr("022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d59", 66, &l1)); - assert(node_id_from_hexstr("0266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c03518", 66, &l2)); - assert(node_id_from_hexstr("035d2b1192dfba134e10e540875d366ebc8bc353d5aa766b80c090b39c3a5d885d", 66, &l3)); - assert(node_id_from_hexstr("0382ce59ebf18be7d84677c2e35f23294b9992ceca95491fcf8a56c6cb2d9de199", 66, &l4)); - assert(short_channel_id_from_str("1x2x0", 5, &scid12)); - assert(short_channel_id_from_str("1x3x0", 5, &scid13)); - assert(short_channel_id_from_str("2x4x0", 5, &scid24)); - assert(short_channel_id_from_str("3x4x0", 5, &scid34)); - - mods = gossmap_localmods_new(tmpctx); - - /* 1->2->4 has capacity 10k sat, 1->3->4 has capacity 5k sat (lower fee!) */ - assert(gossmap_local_addchan(mods, &l1, &l2, scid12, NULL)); - assert(gossmap_local_updatechan(mods, scid12, - /*htlc_min=*/ AMOUNT_MSAT(0), - /*htlc_max=*/ AMOUNT_MSAT(10000000), - /*base_fee=*/ 0, - /*ppm_fee =*/ 1001, - /* delay =*/ 5, - /* enabled=*/ true, - /* dir =*/ 0)); - assert(gossmap_local_addchan(mods, &l2, &l4, scid24, NULL)); - assert(gossmap_local_updatechan(mods, scid24, - AMOUNT_MSAT(0), - AMOUNT_MSAT(10000000), - 0, 1002, 5, - true, - 0)); - assert(gossmap_local_addchan(mods, &l1, &l3, scid13, NULL)); - assert(gossmap_local_updatechan(mods, scid13, - AMOUNT_MSAT(0), - AMOUNT_MSAT(5000000), - 0, 503, 5, - true, - 0)); - assert(gossmap_local_addchan(mods, &l3, &l4, scid34, NULL)); - assert(gossmap_local_updatechan(mods, scid34, - AMOUNT_MSAT(0), - AMOUNT_MSAT(5000000), - 0, 504, 5, - true, - 0)); - - gossmap_apply_localmods(gossmap, mods); - chan_extra_map = tal(tmpctx, struct chan_extra_map); - chan_extra_map_init(chan_extra_map); - /* The local chans have no "capacity", so set them manually. */ - new_chan_extra(chan_extra_map, - scid12, - AMOUNT_MSAT(10000000)); - new_chan_extra(chan_extra_map, - scid24, - AMOUNT_MSAT(10000000)); - new_chan_extra(chan_extra_map, - scid13, - AMOUNT_MSAT(5000000)); - new_chan_extra(chan_extra_map, - scid34, - AMOUNT_MSAT(5000000)); - - bitmap *disabled = tal_arrz( - tmpctx, bitmap, 2 * BITMAP_NWORDS(gossmap_max_chan_idx(gossmap))); - - struct flow **flows; - flows = minflow(tmpctx, gossmap, - gossmap_find_node(gossmap, &l1), - gossmap_find_node(gossmap, &l4), - chan_extra_map, - disabled, - /* Half the capacity */ - AMOUNT_MSAT(1000000), // 1000 sats - /* max_fee = */ AMOUNT_MSAT(10000), // 10 sats - /* min probability = */ 0.8, // 80% - /* base probability = */ 1.0, - /* delay fee factor = */ 0, - /* base fee penalty */ 0, - /* prob cost factor = */ 1, - &errmsg); - if (!flows) { - printf("Minflow has failed with: %s", errmsg); - assert(0 && "minflow failed"); - } - - printf("%s\n", - print_flows(tmpctx,"Simple minflow", gossmap,chan_extra_map, flows)); - - common_shutdown(); -} diff --git a/plugins/renepay/test/run-mcf.c b/plugins/renepay/test/run-mcf.c deleted file mode 100644 index 79c291fe92c4..000000000000 --- a/plugins/renepay/test/run-mcf.c +++ /dev/null @@ -1,607 +0,0 @@ -#include "config.h" - -#define RENEPAY_UNITTEST // logs are written in /tmp/debug.txt -#include "../payment.c" -#include "../flow.c" -#include "../route.c" -#include "../uncertainty.c" -#include "../mcf.c" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* AUTOGENERATED MOCKS START */ -/* Generated stub for disabledmap_add_channel */ -void disabledmap_add_channel(struct disabledmap *p UNNEEDED, - struct short_channel_id_dir scidd UNNEEDED) -{ fprintf(stderr, "disabledmap_add_channel called!\n"); abort(); } -/* Generated stub for disabledmap_add_node */ -void disabledmap_add_node(struct disabledmap *p UNNEEDED, struct node_id node UNNEEDED) -{ fprintf(stderr, "disabledmap_add_node called!\n"); abort(); } -/* Generated stub for disabledmap_channel_is_warned */ -bool disabledmap_channel_is_warned(struct disabledmap *p UNNEEDED, - struct short_channel_id_dir scidd UNNEEDED) -{ fprintf(stderr, "disabledmap_channel_is_warned called!\n"); abort(); } -/* Generated stub for disabledmap_new */ -struct disabledmap *disabledmap_new(const tal_t *ctx UNNEEDED) -{ fprintf(stderr, "disabledmap_new called!\n"); abort(); } -/* Generated stub for disabledmap_reset */ -void disabledmap_reset(struct disabledmap *p UNNEEDED) -{ fprintf(stderr, "disabledmap_reset called!\n"); abort(); } -/* Generated stub for disabledmap_warn_channel */ -void disabledmap_warn_channel(struct disabledmap *p UNNEEDED, - struct short_channel_id_dir scidd UNNEEDED) -{ fprintf(stderr, "disabledmap_warn_channel called!\n"); abort(); } -/* Generated stub for json_add_payment */ -void json_add_payment(struct json_stream *s UNNEEDED, const struct payment *payment UNNEEDED) -{ fprintf(stderr, "json_add_payment called!\n"); abort(); } -/* Generated stub for new_routetracker */ -struct routetracker *new_routetracker(const tal_t *ctx UNNEEDED, struct payment *payment UNNEEDED) -{ fprintf(stderr, "new_routetracker called!\n"); abort(); } -/* Generated stub for pay_plugin */ -struct pay_plugin *pay_plugin; -/* Generated stub for routetracker_cleanup */ -void routetracker_cleanup(struct routetracker *routetracker UNNEEDED) -{ fprintf(stderr, "routetracker_cleanup called!\n"); abort(); } -/* Generated stub for sciddir_or_pubkey_from_node_id */ -bool sciddir_or_pubkey_from_node_id(struct sciddir_or_pubkey *sciddpk UNNEEDED, - const struct node_id *node_id UNNEEDED) -{ fprintf(stderr, "sciddir_or_pubkey_from_node_id called!\n"); abort(); } -/* AUTOGENERATED MOCKS END */ - -static void swap(int *a, int *b) -{ - int temp = *a; - *a = *b; - *b = temp; -} - -/* Canned gossmap, taken from tests/test_gossip.py, with channels mined. - * $> od -tx1 -Anone -v < /tmp/ltests-rtchpzh1/test_gossip_store_compact_noappend_1/lightning-2/regtest/gossip_store | sed 's/ / 0x/g'| cut -c2- | sed -e 's/ /,/g' -e 's/$/,/' - */ -static u8 canned_map[] = { - 0x0c,0x80,0x00,0x01,0xbc,0x09,0x8b,0x67,0xe6,0x00,0x00,0x00,0x00,0x10,0x08,0x00, - 0x00,0x00,0x00,0x00,0x0f,0x42,0x40,0x01,0xb0,0x01,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x22,0x6e, - 0x46,0x11,0x1a,0x0b,0x59,0xca,0xaf,0x12,0x60,0x43,0xeb,0x5b,0xbf,0x28,0xc3,0x4f, - 0x3a,0x5e,0x33,0x2a,0x1f,0xc7,0xb2,0xb7,0x3c,0xf1,0x88,0x91,0x0f,0x00,0x00,0x67, - 0x00,0x00,0x01,0x00,0x00,0x02,0x2d,0x22,0x36,0x20,0xa3,0x59,0xa4,0x7f,0xf7,0xf7, - 0xac,0x44,0x7c,0x85,0xc4,0x6c,0x92,0x3d,0xa5,0x33,0x89,0x22,0x1a,0x00,0x54,0xc1, - 0x1c,0x1e,0x3c,0xa3,0x1d,0x59,0x03,0x5d,0x2b,0x11,0x92,0xdf,0xba,0x13,0x4e,0x10, - 0xe5,0x40,0x87,0x5d,0x36,0x6e,0xbc,0x8b,0xc3,0x53,0xd5,0xaa,0x76,0x6b,0x80,0xc0, - 0x90,0xb3,0x9c,0x3a,0x5d,0x88,0x5d,0x03,0x1b,0x84,0xc5,0x56,0x7b,0x12,0x64,0x40, - 0x99,0x5d,0x3e,0xd5,0xaa,0xba,0x05,0x65,0xd7,0x1e,0x18,0x34,0x60,0x48,0x19,0xff, - 0x9c,0x17,0xf5,0xe9,0xd5,0xdd,0x07,0x8f,0x03,0x1b,0x84,0xc5,0x56,0x7b,0x12,0x64, - 0x40,0x99,0x5d,0x3e,0xd5,0xaa,0xba,0x05,0x65,0xd7,0x1e,0x18,0x34,0x60,0x48,0x19, - 0xff,0x9c,0x17,0xf5,0xe9,0xd5,0xdd,0x07,0x8f,0x00,0x00,0x01,0xb0,0x6e,0x30,0x94, - 0x60,0x65,0x55,0xb3,0x60,0x01,0x00,0x00,0x33,0x57,0x33,0xf5,0x94,0x2d,0xf5,0xd9, - 0x50,0xeb,0x87,0x66,0xde,0xe3,0xa9,0xd6,0x62,0x69,0x22,0x84,0x4e,0xd6,0xae,0x7a, - 0x11,0x0d,0xd7,0xe7,0xed,0xc3,0x2e,0x3f,0x6f,0x3d,0x9a,0xc5,0xcd,0xea,0x23,0xce, - 0x25,0xbb,0x8d,0xbf,0x76,0x1f,0xd3,0xd5,0xfc,0x56,0xc0,0x5b,0x68,0x56,0x31,0x6d, - 0x12,0xe9,0xd3,0x2c,0xa0,0xf0,0x8c,0x69,0xca,0x03,0x06,0xfe,0x71,0x6e,0x7b,0x51, - 0x51,0x31,0x7d,0x64,0x40,0xb7,0x37,0x3d,0x9f,0xbc,0x64,0x6e,0xad,0x48,0xf2,0x16, - 0x3f,0x2b,0x6d,0x51,0x1a,0xfe,0x6a,0x79,0xc7,0x55,0x51,0xc2,0x62,0x0f,0xc8,0x09, - 0x74,0xf2,0xf8,0x64,0x32,0x9d,0x97,0x78,0xa0,0x8c,0xdf,0xbc,0x9f,0x2c,0x9c,0x13, - 0x44,0xc4,0x32,0x70,0x2c,0x66,0x80,0x7c,0xfb,0x4d,0xb6,0x9b,0x80,0xfa,0xe8,0xc3, - 0x3c,0x70,0x14,0x3d,0x94,0x8b,0x36,0x61,0x4d,0x62,0x08,0x91,0xbe,0xe2,0xdf,0x99, - 0xc8,0x6b,0xc6,0x22,0x07,0xc1,0x7e,0x3b,0x91,0x86,0x21,0x4c,0x0c,0xcf,0xf2,0xde, - 0xd5,0x59,0x8a,0xcc,0xc9,0x0e,0xb1,0xd5,0xb2,0xf7,0xa8,0x3c,0xd7,0xf6,0x8d,0x71, - 0x2e,0xa0,0x47,0xd8,0x01,0x9f,0x34,0x30,0x63,0xb0,0xa2,0x36,0x35,0x6a,0x38,0x71, - 0x46,0xf5,0x8f,0xa8,0x32,0xdd,0xc1,0x3c,0x47,0x14,0x52,0x2c,0xbb,0x50,0x3f,0x5f, - 0x3c,0xa8,0xfc,0xec,0x66,0x02,0xbe,0x24,0x38,0xad,0x3f,0x98,0xfa,0x0c,0xee,0xd5, - 0x8f,0xe3,0xa0,0x66,0xd3,0x85,0xfc,0xac,0xd9,0x8c,0x70,0x4b,0x2a,0x8e,0x98,0xa3, - 0xe2,0x0b,0xf7,0x6b,0x35,0xb7,0x36,0x00,0x00,0x06,0x22,0x6e,0x46,0x11,0x1a,0x0b, - 0x59,0xca,0xaf,0x12,0x60,0x43,0xeb,0x5b,0xbf,0x28,0xc3,0x4f,0x3a,0x5e,0x33,0x2a, - 0x1f,0xc7,0xb2,0xb7,0x3c,0xf1,0x88,0x91,0x0f,0x00,0x00,0x67,0x00,0x00,0x01,0x00, - 0x00,0x02,0x2d,0x22,0x36,0x20,0xa3,0x59,0xa4,0x7f,0xf7,0xf7,0xac,0x44,0x7c,0x85, - 0xc4,0x6c,0x92,0x3d,0xa5,0x33,0x89,0x22,0x1a,0x00,0x54,0xc1,0x1c,0x1e,0x3c,0xa3, - 0x1d,0x59,0x03,0x5d,0x2b,0x11,0x92,0xdf,0xba,0x13,0x4e,0x10,0xe5,0x40,0x87,0x5d, - 0x36,0x6e,0xbc,0x8b,0xc3,0x53,0xd5,0xaa,0x76,0x6b,0x80,0xc0,0x90,0xb3,0x9c,0x3a, - 0x5d,0x88,0x5d,0x02,0x90,0x53,0x52,0x1d,0x6e,0xa7,0xa5,0x2c,0xdd,0x55,0xf7,0x33, - 0xd0,0xfb,0x2d,0x07,0x7c,0x03,0x73,0xb0,0x05,0x3b,0x5b,0x81,0x0d,0x92,0x72,0x44, - 0x06,0x1b,0x75,0x73,0x02,0xd6,0x06,0x3d,0x02,0x26,0x91,0xb2,0x49,0x0a,0xb4,0x54, - 0xde,0xe7,0x3a,0x57,0xc6,0xff,0x5d,0x30,0x83,0x52,0xb4,0x61,0xec,0xe6,0x9f,0x3c, - 0x28,0x4f,0x2c,0x24,0x12,0x00,0x00,0x00,0x0a,0x91,0x11,0x83,0xf6,0x00,0x00,0x00, - 0x00,0x10,0x05,0x00,0x00,0x00,0x00,0x00,0x0f,0x42,0x40,0x80,0x00,0x00,0x8a,0xc5, - 0x33,0xff,0x38,0x65,0x55,0xb3,0x60,0x01,0x02,0x2f,0xd9,0x23,0x60,0x1e,0x1c,0xa0, - 0xac,0xe5,0x06,0x8c,0xe4,0x8e,0x14,0xf3,0xcd,0x31,0x44,0x16,0xc4,0x0d,0x2e,0x14, - 0x8c,0xa1,0xc8,0x4f,0xa6,0xa8,0xe4,0x64,0x9b,0x45,0x79,0xd1,0xb5,0x2f,0x04,0x19, - 0x86,0xe5,0x5c,0x99,0x43,0xf1,0xd0,0xf3,0x6f,0x52,0xd6,0x88,0xf0,0x9b,0x9c,0x58, - 0x98,0x69,0x0d,0x4e,0x76,0x3e,0xbd,0x6e,0x95,0x06,0x22,0x6e,0x46,0x11,0x1a,0x0b, - 0x59,0xca,0xaf,0x12,0x60,0x43,0xeb,0x5b,0xbf,0x28,0xc3,0x4f,0x3a,0x5e,0x33,0x2a, - 0x1f,0xc7,0xb2,0xb7,0x3c,0xf1,0x88,0x91,0x0f,0x00,0x00,0x67,0x00,0x00,0x01,0x00, - 0x00,0x65,0x55,0xb3,0x60,0x01,0x00,0x00,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x0a,0x00,0x00,0x00,0x00,0x3b,0x02,0x33, - 0x80,0x00,0x00,0x00,0x95,0x97,0xd7,0xa4,0x39,0x65,0x55,0xb3,0x65,0x01,0x01,0x36, - 0x59,0x20,0x77,0x0e,0xf4,0x73,0x10,0xd7,0xb6,0x59,0x5a,0x7c,0xbe,0xd0,0x56,0x51, - 0x3d,0x97,0xbe,0x84,0xb7,0x02,0xb5,0x89,0x72,0xbd,0xb3,0x19,0x2a,0x54,0x5f,0x57, - 0x52,0x09,0x1d,0xff,0x0f,0xe6,0x70,0x1e,0x71,0x23,0xaa,0x3f,0x98,0x89,0x86,0x35, - 0x87,0xc2,0x66,0xd9,0x99,0xbd,0xa2,0x7e,0x16,0x12,0xd9,0x6b,0xc4,0xd3,0x0c,0x00, - 0x07,0x88,0xa0,0x00,0x0a,0x0a,0x69,0xa2,0x65,0x55,0xb3,0x65,0x02,0x2d,0x22,0x36, - 0x20,0xa3,0x59,0xa4,0x7f,0xf7,0xf7,0xac,0x44,0x7c,0x85,0xc4,0x6c,0x92,0x3d,0xa5, - 0x33,0x89,0x22,0x1a,0x00,0x54,0xc1,0x1c,0x1e,0x3c,0xa3,0x1d,0x59,0x02,0x2d,0x22, - 0x53,0x49,0x4c,0x45,0x4e,0x54,0x41,0x52,0x54,0x49,0x53,0x54,0x2d,0x31,0x2d,0x31, - 0x2d,0x67,0x63,0x63,0x65,0x34,0x66,0x36,0x38,0x2d,0x6d,0x6f,0x64,0x64,0x65,0x64, - 0x00,0x00,0x80,0x00,0x00,0x8a,0xd0,0xde,0x20,0xc6,0x65,0x55,0xb3,0x60,0x01,0x02, - 0x11,0xd6,0x4b,0xc5,0x88,0x33,0x4b,0x6c,0x1e,0x0c,0x4c,0x5c,0x65,0x69,0x35,0x66, - 0x45,0x80,0xbc,0x4b,0x56,0x3d,0x98,0x2c,0xb3,0x45,0xef,0x22,0x44,0x6e,0xca,0x6c, - 0x1f,0xe9,0x2a,0xf2,0x4a,0xe1,0x26,0xdb,0x15,0x6f,0x7e,0x3b,0xdd,0xd3,0x40,0x54, - 0xb2,0xc7,0x07,0xfe,0x67,0xb0,0xf3,0x35,0x56,0x25,0xc6,0x53,0xfd,0x54,0x56,0x5e, - 0x06,0x22,0x6e,0x46,0x11,0x1a,0x0b,0x59,0xca,0xaf,0x12,0x60,0x43,0xeb,0x5b,0xbf, - 0x28,0xc3,0x4f,0x3a,0x5e,0x33,0x2a,0x1f,0xc7,0xb2,0xb7,0x3c,0xf1,0x88,0x91,0x0f, - 0x00,0x00,0x67,0x00,0x00,0x01,0x00,0x00,0x65,0x55,0xb3,0x60,0x01,0x01,0x00,0x06, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x0a, - 0x00,0x00,0x00,0x00,0x3b,0x02,0x33,0x80,0x00,0x00,0x00,0x95,0x87,0xf8,0x92,0xf3, - 0x65,0x55,0xb3,0x65,0x01,0x01,0x32,0x6c,0xde,0x4a,0xc5,0xe8,0xd4,0xa0,0xed,0xbc, - 0x2c,0x13,0x5e,0xb9,0x1e,0xc3,0xc3,0x86,0xcb,0x75,0xeb,0x6f,0xce,0xc5,0xc5,0x57, - 0x01,0x1c,0x9c,0xb7,0x32,0x17,0x01,0x10,0x8c,0xdd,0x04,0x31,0x78,0xae,0xb4,0x88, - 0x8d,0xf8,0xe8,0x35,0x90,0x69,0x91,0x84,0xd0,0x16,0xd8,0x44,0xbc,0xde,0x37,0xe8, - 0x4e,0x1a,0x95,0xb6,0xa6,0x73,0x00,0x07,0x88,0xa0,0x00,0x0a,0x0a,0x69,0xa2,0x65, - 0x55,0xb3,0x65,0x03,0x5d,0x2b,0x11,0x92,0xdf,0xba,0x13,0x4e,0x10,0xe5,0x40,0x87, - 0x5d,0x36,0x6e,0xbc,0x8b,0xc3,0x53,0xd5,0xaa,0x76,0x6b,0x80,0xc0,0x90,0xb3,0x9c, - 0x3a,0x5d,0x88,0x5d,0x03,0x5d,0x2b,0x48,0x4f,0x50,0x50,0x49,0x4e,0x47,0x46,0x49, - 0x52,0x45,0x2d,0x63,0x31,0x2d,0x31,0x2d,0x67,0x63,0x63,0x65,0x34,0x66,0x36,0x38, - 0x2d,0x6d,0x6f,0x64,0x64,0x65,0x64,0x00,0x00,0x00,0x00,0x00,0x8a,0xcb,0xd4,0xc7, - 0xa1,0x65,0x55,0xb3,0x65,0x01,0x02,0x1d,0x3f,0x42,0x7f,0x3d,0xdb,0x58,0x2b,0xcb, - 0x78,0x5d,0x24,0xf1,0x67,0xc6,0xc7,0xd8,0x6e,0x6c,0x5b,0xf8,0xfb,0x27,0x17,0x58, - 0xaa,0x7e,0x46,0x86,0x49,0x66,0x21,0x47,0x76,0xbd,0xf2,0x2d,0xae,0x29,0xf0,0x6f, - 0x17,0x6e,0xf2,0x7f,0x01,0xda,0x16,0xa9,0x1d,0x6a,0x61,0x4c,0x41,0x71,0x19,0x4d, - 0x37,0xac,0x8a,0x28,0xd8,0x62,0xfb,0x06,0x22,0x6e,0x46,0x11,0x1a,0x0b,0x59,0xca, - 0xaf,0x12,0x60,0x43,0xeb,0x5b,0xbf,0x28,0xc3,0x4f,0x3a,0x5e,0x33,0x2a,0x1f,0xc7, - 0xb2,0xb7,0x3c,0xf1,0x88,0x91,0x0f,0x00,0x00,0x67,0x00,0x00,0x01,0x00,0x00,0x65, - 0x55,0xb3,0x65,0x01,0x00,0x00,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x14,0x00,0x00,0x03,0xe8,0x00,0x00,0x00,0x00,0x3b,0x02,0x33,0x80,0x80, - 0x00,0x01,0xbc,0x39,0xdb,0xc7,0xdf,0x00,0x00,0x00,0x00,0x10,0x08,0x00,0x00,0x00, - 0x00,0x00,0x0f,0x42,0x40,0x01,0xb0,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x22,0x6e,0x46,0x11, - 0x1a,0x0b,0x59,0xca,0xaf,0x12,0x60,0x43,0xeb,0x5b,0xbf,0x28,0xc3,0x4f,0x3a,0x5e, - 0x33,0x2a,0x1f,0xc7,0xb2,0xb7,0x3c,0xf1,0x88,0x91,0x0f,0x00,0x00,0x6e,0x00,0x00, - 0x01,0x00,0x00,0x02,0x2d,0x22,0x36,0x20,0xa3,0x59,0xa4,0x7f,0xf7,0xf7,0xac,0x44, - 0x7c,0x85,0xc4,0x6c,0x92,0x3d,0xa5,0x33,0x89,0x22,0x1a,0x00,0x54,0xc1,0x1c,0x1e, - 0x3c,0xa3,0x1d,0x59,0x02,0x66,0xe4,0x59,0x8d,0x1d,0x3c,0x41,0x5f,0x57,0x2a,0x84, - 0x88,0x83,0x0b,0x60,0xf7,0xe7,0x44,0xed,0x92,0x35,0xeb,0x0b,0x1b,0xa9,0x32,0x83, - 0xb3,0x15,0xc0,0x35,0x18,0x03,0x1b,0x84,0xc5,0x56,0x7b,0x12,0x64,0x40,0x99,0x5d, - 0x3e,0xd5,0xaa,0xba,0x05,0x65,0xd7,0x1e,0x18,0x34,0x60,0x48,0x19,0xff,0x9c,0x17, - 0xf5,0xe9,0xd5,0xdd,0x07,0x8f,0x03,0x1b,0x84,0xc5,0x56,0x7b,0x12,0x64,0x40,0x99, - 0x5d,0x3e,0xd5,0xaa,0xba,0x05,0x65,0xd7,0x1e,0x18,0x34,0x60,0x48,0x19,0xff,0x9c, - 0x17,0xf5,0xe9,0xd5,0xdd,0x07,0x8f,0x80,0x00,0x00,0x8e,0x85,0x67,0x9b,0xcf,0x00, - 0x00,0x00,0x00,0x10,0x06,0x00,0x8a,0x01,0x02,0x27,0x9e,0x4e,0x43,0x39,0xa6,0x92, - 0x19,0x35,0x50,0x19,0xbb,0x51,0x5b,0xf9,0xac,0xdb,0xda,0x9c,0xde,0x81,0x8b,0x56, - 0x2e,0x0a,0x3d,0xd5,0xb0,0x5f,0x10,0x2e,0x6c,0x22,0x55,0x7e,0x07,0xc2,0x5f,0x4b, - 0x9c,0xc1,0x21,0x6e,0x07,0x66,0x41,0x60,0xde,0x3e,0xe2,0x24,0xa5,0x9e,0xec,0xaf, - 0xd7,0xcc,0x3f,0x87,0x7c,0x32,0x29,0xca,0xe7,0x06,0x22,0x6e,0x46,0x11,0x1a,0x0b, - 0x59,0xca,0xaf,0x12,0x60,0x43,0xeb,0x5b,0xbf,0x28,0xc3,0x4f,0x3a,0x5e,0x33,0x2a, - 0x1f,0xc7,0xb2,0xb7,0x3c,0xf1,0x88,0x91,0x0f,0x00,0x00,0x6e,0x00,0x00,0x01,0x00, - 0x00,0x65,0x55,0xb3,0x6c,0x01,0x00,0x00,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x0a,0x00,0x00,0x00,0x00,0x3b,0x02,0x33, - 0x80,0x80,0x00,0x00,0x8e,0xca,0xa4,0x8e,0x70,0x00,0x00,0x00,0x00,0x10,0x06,0x00, - 0x8a,0x01,0x02,0x4e,0x3f,0x3b,0x6d,0xda,0xcc,0xd3,0xef,0x5f,0xaf,0x26,0x76,0x16, - 0x64,0xa2,0x82,0x97,0xe8,0xb4,0xe4,0xb1,0x2d,0xec,0xa1,0x9e,0x91,0x69,0xd3,0xde, - 0xe9,0x58,0xc7,0x19,0x06,0x90,0x42,0x86,0x97,0xf3,0x88,0xca,0x35,0xd5,0xec,0x79, - 0x5e,0x59,0x33,0x31,0xf4,0x0c,0xdb,0x55,0x5d,0x78,0xd7,0x22,0x59,0xa2,0xe5,0x8d, - 0xeb,0x65,0x41,0x06,0x22,0x6e,0x46,0x11,0x1a,0x0b,0x59,0xca,0xaf,0x12,0x60,0x43, - 0xeb,0x5b,0xbf,0x28,0xc3,0x4f,0x3a,0x5e,0x33,0x2a,0x1f,0xc7,0xb2,0xb7,0x3c,0xf1, - 0x88,0x91,0x0f,0x00,0x00,0x6e,0x00,0x00,0x01,0x00,0x00,0x65,0x55,0xb3,0x6c,0x01, - 0x01,0x00,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00, - 0x00,0x00,0x0a,0x00,0x00,0x00,0x00,0x3b,0x02,0x33,0x80,0x00,0x00,0x00,0x8a,0xbf, - 0x0d,0x7a,0xbc,0x65,0x55,0xb3,0x6f,0x01,0x02,0x59,0xc9,0x36,0x84,0xaa,0x13,0x70, - 0x73,0x7d,0xa7,0xe6,0x8a,0x72,0x05,0x15,0xa9,0x06,0x69,0xe0,0xc0,0x0d,0x15,0x6d, - 0x22,0x5c,0xd6,0x1a,0x3e,0x56,0xaa,0x9d,0x98,0x68,0x5a,0x3c,0xdc,0x38,0x71,0xee, - 0x0d,0x02,0x3e,0x2f,0xd0,0x97,0xd7,0xab,0xe2,0x0d,0xdf,0xf5,0xa6,0xd1,0x9c,0xb6, - 0xb9,0x30,0xd6,0xdd,0x10,0xa7,0xa3,0xce,0x5e,0x06,0x22,0x6e,0x46,0x11,0x1a,0x0b, - 0x59,0xca,0xaf,0x12,0x60,0x43,0xeb,0x5b,0xbf,0x28,0xc3,0x4f,0x3a,0x5e,0x33,0x2a, - 0x1f,0xc7,0xb2,0xb7,0x3c,0xf1,0x88,0x91,0x0f,0x00,0x00,0x67,0x00,0x00,0x01,0x00, - 0x00,0x65,0x55,0xb3,0x6f,0x01,0x01,0x00,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x14,0x00,0x00,0x03,0xe8,0x00,0x00,0x00,0x00,0x3b,0x02,0x33, - 0x80,0x80,0x00,0x00,0x8e,0x80,0x00,0x75,0xd3,0x00,0x00,0x00,0x00,0x10,0x06,0x00, - 0x8a,0x01,0x02,0x26,0x62,0x25,0x68,0x2d,0x27,0x06,0x9a,0x3b,0xaa,0x1a,0x43,0x93, - 0xac,0xb5,0x05,0x14,0x1c,0x09,0x22,0x8d,0xfb,0x54,0x43,0x73,0x3a,0x88,0xaa,0xa3, - 0x18,0xc8,0xd8,0x0d,0x36,0xd4,0x26,0x10,0xe9,0x82,0xa6,0x3c,0xb4,0x38,0x24,0xaf, - 0xdc,0x06,0xd5,0x3d,0xe3,0x7a,0xe8,0x39,0xdd,0xd4,0x5f,0xd8,0x92,0xf5,0x4b,0xe7, - 0x8d,0xf7,0xab,0x06,0x22,0x6e,0x46,0x11,0x1a,0x0b,0x59,0xca,0xaf,0x12,0x60,0x43, - 0xeb,0x5b,0xbf,0x28,0xc3,0x4f,0x3a,0x5e,0x33,0x2a,0x1f,0xc7,0xb2,0xb7,0x3c,0xf1, - 0x88,0x91,0x0f,0x00,0x00,0x6e,0x00,0x00,0x01,0x00,0x00,0x65,0x55,0xb3,0x71,0x01, - 0x01,0x00,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x14,0x00, - 0x00,0x03,0xe8,0x00,0x00,0x00,0x00,0x3b,0x02,0x33,0x80,0x80,0x00,0x00,0x8e,0xdc, - 0x5e,0x38,0x7a,0x00,0x00,0x00,0x00,0x10,0x06,0x00,0x8a,0x01,0x02,0x5d,0xf9,0x14, - 0xa0,0xd1,0x46,0x69,0x9a,0x2a,0x97,0xf7,0xb3,0x83,0xf0,0x30,0x73,0x48,0x5e,0x7b, - 0x3e,0x6c,0x45,0xd3,0xf1,0x51,0xf8,0xbc,0x4b,0xd7,0xae,0x02,0xd9,0x57,0x51,0x73, - 0x8f,0x0f,0xd0,0xdd,0x38,0x36,0x48,0xcd,0xda,0xea,0x01,0x24,0x16,0x3d,0x14,0x7e, - 0x5a,0x9e,0xac,0xcd,0x81,0xd1,0x96,0x30,0xd9,0xbc,0xa6,0xe4,0xe8,0x06,0x22,0x6e, - 0x46,0x11,0x1a,0x0b,0x59,0xca,0xaf,0x12,0x60,0x43,0xeb,0x5b,0xbf,0x28,0xc3,0x4f, - 0x3a,0x5e,0x33,0x2a,0x1f,0xc7,0xb2,0xb7,0x3c,0xf1,0x88,0x91,0x0f,0x00,0x00,0x6e, - 0x00,0x00,0x01,0x00,0x00,0x65,0x55,0xb3,0x71,0x01,0x00,0x00,0x06,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x14,0x00,0x00,0x03,0xe8,0x00,0x00,0x00, - 0x00,0x3b,0x02,0x33,0x80,0x00,0x00,0x01,0xb0,0xb8,0x17,0x71,0xe9,0x65,0x55,0xb3, - 0x71,0x01,0x00,0x0d,0xef,0xc0,0x12,0x4f,0xd4,0xe4,0x1c,0xce,0xc3,0xf4,0x83,0x17, - 0x21,0x7e,0x33,0x11,0x5a,0x8f,0x8c,0x39,0xae,0xbb,0x06,0x2b,0x4e,0x73,0xa6,0x52, - 0xee,0x0e,0x61,0x03,0xf9,0xf1,0x16,0x7b,0x22,0xbd,0xcb,0x92,0xcf,0x82,0x2d,0x0f, - 0xcc,0x26,0xcb,0x11,0x9f,0x0a,0xf5,0x23,0xf4,0x26,0x97,0x86,0x7e,0x21,0x51,0x85, - 0x16,0x3a,0x0e,0x4b,0x1f,0x64,0xf3,0x62,0xf4,0x2f,0x13,0x54,0x5c,0x9a,0xed,0xd6, - 0x9d,0x5d,0xf5,0x17,0x85,0xcc,0x5d,0x53,0xa4,0x84,0x36,0x9b,0xe9,0xa7,0x4a,0x04, - 0x1f,0x8c,0x1c,0x28,0xc7,0x1e,0xab,0xb3,0xf2,0x53,0x3d,0xf8,0xb6,0xcd,0xd8,0x45, - 0x6e,0xc3,0x77,0xdb,0xb1,0x2b,0xa5,0xdd,0xc9,0xbf,0x69,0xa7,0xdf,0x98,0x2c,0x54, - 0xa7,0xa8,0xf5,0x4f,0xc3,0xd1,0x57,0x81,0x98,0x60,0xa0,0xb6,0x32,0x83,0x08,0x65, - 0x1e,0x79,0x24,0xf4,0xbe,0xfe,0x14,0xa6,0xfd,0x31,0x9b,0xfa,0x15,0x6f,0x1b,0xab, - 0x64,0x8b,0x69,0x48,0xb4,0x2e,0x99,0xca,0xd7,0x46,0x97,0x57,0x17,0x1d,0x5d,0x53, - 0xe5,0xde,0xae,0xbb,0x2d,0x74,0x86,0xa5,0xe5,0x8e,0x02,0x5c,0x2c,0x8d,0x0d,0x78, - 0xdc,0xf1,0xa0,0x05,0xb8,0xbd,0x64,0x7d,0x51,0x47,0x5e,0x39,0xaa,0x08,0x2d,0xf2, - 0xd0,0x1a,0x96,0x59,0xab,0x88,0x94,0xa2,0xf7,0x2e,0x09,0x3b,0xd0,0xed,0xfc,0xb4, - 0x1b,0xaa,0xba,0x34,0xf8,0x7b,0x11,0x51,0x36,0x7b,0x13,0x84,0x6f,0xc9,0x4a,0x58, - 0x92,0x3c,0x0c,0xa2,0x4c,0x8f,0xc5,0x78,0xb4,0x4a,0xad,0x98,0x08,0x4e,0x4a,0xf3, - 0x7a,0x28,0x80,0x00,0x00,0x06,0x22,0x6e,0x46,0x11,0x1a,0x0b,0x59,0xca,0xaf,0x12, - 0x60,0x43,0xeb,0x5b,0xbf,0x28,0xc3,0x4f,0x3a,0x5e,0x33,0x2a,0x1f,0xc7,0xb2,0xb7, - 0x3c,0xf1,0x88,0x91,0x0f,0x00,0x00,0x6e,0x00,0x00,0x01,0x00,0x00,0x02,0x2d,0x22, - 0x36,0x20,0xa3,0x59,0xa4,0x7f,0xf7,0xf7,0xac,0x44,0x7c,0x85,0xc4,0x6c,0x92,0x3d, - 0xa5,0x33,0x89,0x22,0x1a,0x00,0x54,0xc1,0x1c,0x1e,0x3c,0xa3,0x1d,0x59,0x02,0x66, - 0xe4,0x59,0x8d,0x1d,0x3c,0x41,0x5f,0x57,0x2a,0x84,0x88,0x83,0x0b,0x60,0xf7,0xe7, - 0x44,0xed,0x92,0x35,0xeb,0x0b,0x1b,0xa9,0x32,0x83,0xb3,0x15,0xc0,0x35,0x18,0x03, - 0xbb,0xee,0x60,0xc3,0x95,0x05,0x6b,0x8a,0x12,0x01,0xe0,0x6e,0xd7,0x9e,0x29,0x14, - 0xc1,0x1a,0x61,0xd7,0xb1,0xaa,0x78,0x18,0x46,0x46,0x8d,0x02,0x48,0x9d,0xba,0x69, - 0x02,0x32,0x42,0x66,0xde,0x84,0x03,0xb3,0xab,0x15,0x7a,0x09,0xf1,0xf7,0x84,0xd5, - 0x87,0xaf,0x61,0x83,0x1c,0x99,0x8c,0x15,0x1b,0xcc,0x21,0xbb,0x74,0xc2,0xb2,0x31, - 0x4b,0x00,0x00,0x00,0x0a,0x91,0x11,0x83,0xf6,0x00,0x00,0x00,0x00,0x10,0x05,0x00, - 0x00,0x00,0x00,0x00,0x0f,0x42,0x40,0x00,0x00,0x00,0x8a,0xe1,0x31,0x6b,0x76,0x65, - 0x55,0xb3,0x71,0x01,0x02,0x5d,0xf9,0x14,0xa0,0xd1,0x46,0x69,0x9a,0x2a,0x97,0xf7, - 0xb3,0x83,0xf0,0x30,0x73,0x48,0x5e,0x7b,0x3e,0x6c,0x45,0xd3,0xf1,0x51,0xf8,0xbc, - 0x4b,0xd7,0xae,0x02,0xd9,0x57,0x51,0x73,0x8f,0x0f,0xd0,0xdd,0x38,0x36,0x48,0xcd, - 0xda,0xea,0x01,0x24,0x16,0x3d,0x14,0x7e,0x5a,0x9e,0xac,0xcd,0x81,0xd1,0x96,0x30, - 0xd9,0xbc,0xa6,0xe4,0xe8,0x06,0x22,0x6e,0x46,0x11,0x1a,0x0b,0x59,0xca,0xaf,0x12, - 0x60,0x43,0xeb,0x5b,0xbf,0x28,0xc3,0x4f,0x3a,0x5e,0x33,0x2a,0x1f,0xc7,0xb2,0xb7, - 0x3c,0xf1,0x88,0x91,0x0f,0x00,0x00,0x6e,0x00,0x00,0x01,0x00,0x00,0x65,0x55,0xb3, - 0x71,0x01,0x00,0x00,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x14,0x00,0x00,0x03,0xe8,0x00,0x00,0x00,0x00,0x3b,0x02,0x33,0x80,0x00,0x00,0x00, - 0x8a,0xbd,0x6f,0x26,0xdf,0x65,0x55,0xb3,0x71,0x01,0x02,0x26,0x62,0x25,0x68,0x2d, - 0x27,0x06,0x9a,0x3b,0xaa,0x1a,0x43,0x93,0xac,0xb5,0x05,0x14,0x1c,0x09,0x22,0x8d, - 0xfb,0x54,0x43,0x73,0x3a,0x88,0xaa,0xa3,0x18,0xc8,0xd8,0x0d,0x36,0xd4,0x26,0x10, - 0xe9,0x82,0xa6,0x3c,0xb4,0x38,0x24,0xaf,0xdc,0x06,0xd5,0x3d,0xe3,0x7a,0xe8,0x39, - 0xdd,0xd4,0x5f,0xd8,0x92,0xf5,0x4b,0xe7,0x8d,0xf7,0xab,0x06,0x22,0x6e,0x46,0x11, - 0x1a,0x0b,0x59,0xca,0xaf,0x12,0x60,0x43,0xeb,0x5b,0xbf,0x28,0xc3,0x4f,0x3a,0x5e, - 0x33,0x2a,0x1f,0xc7,0xb2,0xb7,0x3c,0xf1,0x88,0x91,0x0f,0x00,0x00,0x6e,0x00,0x00, - 0x01,0x00,0x00,0x65,0x55,0xb3,0x71,0x01,0x01,0x00,0x06,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x14,0x00,0x00,0x03,0xe8,0x00,0x00,0x00,0x00,0x3b, - 0x02,0x33,0x80,0x00,0x00,0x00,0x95,0xf6,0xd7,0x24,0x69,0x65,0x55,0xb3,0x79,0x01, - 0x01,0x42,0x62,0x2b,0xf0,0x0a,0x44,0xc3,0xe0,0x78,0x3c,0xa7,0x08,0x5a,0xa0,0xaa, - 0x9d,0xb2,0xdc,0x2e,0xa7,0x29,0x0a,0x39,0x3d,0x01,0x57,0xbf,0x34,0x23,0x0f,0xb9, - 0x19,0x33,0x6f,0xc2,0xfb,0x9f,0x25,0xac,0xc4,0xc9,0x47,0xb7,0x41,0x92,0xdb,0x2f, - 0xd5,0xf0,0x1f,0x24,0x32,0xdf,0x38,0x30,0x89,0x93,0xeb,0xc9,0xec,0xf1,0x09,0x51, - 0xbb,0x00,0x07,0x88,0xa0,0x00,0x0a,0x0a,0x69,0xa2,0x65,0x55,0xb3,0x79,0x02,0x66, - 0xe4,0x59,0x8d,0x1d,0x3c,0x41,0x5f,0x57,0x2a,0x84,0x88,0x83,0x0b,0x60,0xf7,0xe7, - 0x44,0xed,0x92,0x35,0xeb,0x0b,0x1b,0xa9,0x32,0x83,0xb3,0x15,0xc0,0x35,0x18,0x02, - 0x66,0xe4,0x4a,0x55,0x4e,0x49,0x4f,0x52,0x42,0x45,0x41,0x4d,0x2d,0x72,0x63,0x31, - 0x2d,0x31,0x2d,0x67,0x63,0x63,0x65,0x34,0x66,0x36,0x38,0x2d,0x6d,0x6f,0x64,0x64, - 0x65,0x64,0x00,0x00, -}; - -/* not_mcf sets NDEBUG, so assert() is useless */ -#define ASSERT(x) do { if (!(x)) abort(); } while(0) - -static const char *print_flows( - const tal_t *ctx, - const char *desc, - const struct gossmap *gossmap, - struct flow **flows) -{ - tal_t *this_ctx = tal(ctx,tal_t); - char *buff = tal_fmt(ctx,"%s: %zu subflows\n", desc, tal_count(flows)); - for (size_t i = 0; i < tal_count(flows); i++) { - struct amount_msat fee, delivered; - tal_append_fmt(&buff," "); - for (size_t j = 0; j < tal_count(flows[i]->path); j++) { - struct short_channel_id scid - = gossmap_chan_scid(gossmap, - flows[i]->path[j]); - tal_append_fmt(&buff,"%s%s", j ? "->" : "", - fmt_short_channel_id(this_ctx, scid)); - } - delivered = flows[i]->amount; - if (!flow_fee(&fee, flows[i])) - abort(); - tal_append_fmt(&buff," prob %.2f, %s delivered with fee %s\n", - flows[i]->success_prob, - fmt_amount_msat(this_ctx, delivered), - fmt_amount_msat(this_ctx, fee)); - } - tal_free(this_ctx); - return buff; -} - -static void remove_file(char *fname) { assert(!remove(fname)); } - -int main(int argc, char *argv[]) -{ - int fd; - char *gossfile; - struct gossmap *gossmap; - struct node_id l1, l2, l3; - struct flow **flows; - struct route **routes; - struct short_channel_id scid12, scid23; - struct sha256 payment_hash; - struct amount_msat *amounts; - char *errmsg; - - if (!hex_decode("0001020304050607080900010203040506070809000102030405060708090102", - strlen("0001020304050607080900010203040506070809000102030405060708090102"), - &payment_hash, sizeof(payment_hash))) - abort(); - - common_setup(argv[0]); - - fd = tmpdir_mkstemp(tmpctx, "run-not_mcf.XXXXXX", &gossfile); - tal_add_destructor(gossfile, remove_file); - assert(write_all(fd, canned_map, sizeof(canned_map))); - - gossmap = gossmap_load(tmpctx, gossfile, NULL); - assert(gossmap); - - /* There is a public channel 2<->3 (103x1x0), and 1<->2 (110x1x1). */ - assert(node_id_from_hexstr("0266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c03518", 66, &l1)); - assert(node_id_from_hexstr("022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d59", 66, &l2)); - assert(node_id_from_hexstr("035d2b1192dfba134e10e540875d366ebc8bc353d5aa766b80c090b39c3a5d885d", 66, &l3)); - assert(short_channel_id_from_str("110x1x0", 7, &scid12)); - assert(short_channel_id_from_str("103x1x0", 7, &scid23)); - - struct uncertainty *uncertainty = uncertainty_new(tmpctx); - int skipped_count = uncertainty_update(uncertainty, gossmap); - assert(skipped_count == 0); - - bitmap *disabled = tal_arrz( - tmpctx, bitmap, 2 * BITMAP_NWORDS(gossmap_max_chan_idx(gossmap))); - - printf("All set, now let's call minflow ...\n"); - - flows = minflow(tmpctx, gossmap, - gossmap_find_node(gossmap, &l1), - gossmap_find_node(gossmap, &l3), - uncertainty_get_chan_extra_map(uncertainty), - disabled, - /* Half the capacity */ - AMOUNT_MSAT(500000000), - /* max_fee = */ AMOUNT_MSAT(1000000), // 1k sats - /* min probability = */ 0.1, - /* base probability = */ 1.0, - /* delay fee factor = */ 1, - /* base fee penalty */ 1, - /* prob cost factor = */ 10, - &errmsg); - printf("minflow completed.\n"); - - if (!flows) { - printf("Minflow has failed with: %s", errmsg); - assert(0 && "minflow failed"); - } - - routes = flows_to_routes(tmpctx, 1, 1, payment_hash, 10, gossmap, flows); - assert(routes); - for (size_t i = 0; i < tal_count(routes); i++) { - uncertainty_commit_htlcs(uncertainty, routes[i]); - } - - printf("%s\n", - print_flows(tmpctx,"Flow via single path l1->l2->l3", gossmap, flows)); - - - printf("Checking results.\n"); - /* Should go 1->2->3 */ - amounts = tal_flow_amounts(tmpctx, flows[0], true); - assert(amounts); - assert(tal_count(flows) == 1); - assert(tal_count(flows[0]->path) == 2); - assert(tal_count(flows[0]->dirs) == 2); - assert(tal_count(amounts) == 2); - - assert(flows[0]->path[0] == gossmap_find_chan(gossmap, &scid12)); - assert(flows[0]->path[1] == gossmap_find_chan(gossmap, &scid23)); - assert(flows[0]->dirs[0] == 1); - assert(flows[0]->dirs[1] == 0); - assert(amount_msat_eq(amounts[1], AMOUNT_MSAT(500000000))); - /* fee_base_msat == 20, fee_proportional_millionths == 1000 */ - assert(amount_msat_eq(amounts[0], AMOUNT_MSAT(500000000 + 500000 + 20))); - - /* Each one has probability ~ 0.5 */ - assert(flows[0]->success_prob > 0.249); - assert(flows[0]->success_prob <= 0.251); - - - /* Should have filled in some extra data! */ - struct chan_extra *ce = uncertainty_find_channel(uncertainty, scid12); - assert(short_channel_id_eq(ce->scid, scid12)); - /* l1->l2 dir is 1 */ - assert(ce->half[1].num_htlcs == 1); - assert(amount_msat_eq(ce->half[1].htlc_total, AMOUNT_MSAT(500000000 + 500000 + 20))); - assert(amount_msat_eq(ce->half[1].known_min, AMOUNT_MSAT(0))); - assert(amount_msat_eq(ce->half[1].known_max, AMOUNT_MSAT(1000000000))); - assert(ce->half[0].num_htlcs == 0); - assert(amount_msat_eq(ce->half[0].htlc_total, AMOUNT_MSAT(0))); - assert(amount_msat_eq(ce->half[0].known_min, AMOUNT_MSAT(0))); - assert(amount_msat_eq(ce->half[0].known_max, AMOUNT_MSAT(1000000000))); - - ce = uncertainty_find_channel(uncertainty, scid23); - assert(short_channel_id_eq(ce->scid, scid23)); - /* l2->l3 dir is 0 */ - assert(ce->half[0].num_htlcs == 1); - assert(amount_msat_eq(ce->half[0].htlc_total, AMOUNT_MSAT(500000000))); - assert(amount_msat_eq(ce->half[0].known_min, AMOUNT_MSAT(0))); - assert(amount_msat_eq(ce->half[0].known_max, AMOUNT_MSAT(1000000000))); - assert(ce->half[1].num_htlcs == 0); - assert(amount_msat_eq(ce->half[1].htlc_total, AMOUNT_MSAT(0))); - assert(amount_msat_eq(ce->half[1].known_min, AMOUNT_MSAT(0))); - assert(amount_msat_eq(ce->half[1].known_max, AMOUNT_MSAT(1000000000))); - - /* Clear that */ - for (size_t i = 0; i < tal_count(routes); i++) { - uncertainty_remove_htlcs(uncertainty, routes[i]); - } - - // /* Now try adding a local channel scid */ - - struct short_channel_id scid13; - struct gossmap_localmods *mods = gossmap_localmods_new(tmpctx); - assert(short_channel_id_from_str("111x1x1", 7, &scid13)); - - /* 400,000sat channel from 1->3, basefee 0, ppm 1000, delay 5 */ - assert(gossmap_local_addchan(mods, &l1, &l3, scid13, NULL)); - assert(gossmap_local_updatechan(mods, scid13, - AMOUNT_MSAT(0), - AMOUNT_MSAT(400000000), - 0, 1000, 5, - true, - 0)); - - /* Apply changes, check they work. */ - gossmap_apply_localmods(gossmap, mods); - struct gossmap_chan *local_chan = gossmap_find_chan(gossmap, &scid13); - assert(local_chan); - - /* The local chans have no "capacity", so set it manually. */ - uncertainty_add_channel(uncertainty, scid13, AMOUNT_MSAT(400000000)); - - // flows = minflow(tmpctx, gossmap, - // gossmap_find_node(gossmap, &l1), - // gossmap_find_node(gossmap, &l3), - // chan_extra_map, NULL, - // /* This will go first via 1-2-3, then 1->3. */ - // AMOUNT_MSAT(500000000), - // /* max_fee = */ AMOUNT_MSAT(1000000), // 1k sats - // /* min probability = */ 0.4, - // /* delay fee factor = */ 1, - // /* base fee penalty */ 1, - // /* prob cost factor = */ 10); - - // print_flows("Flow via two paths, low mu", gossmap, flows); - - // assert(tal_count(flows) == 2); - // - // if(tal_count(flows[0]->path)path)) - // { - // struct flow* tmp = flows[0]; - // flows[0] = flows[1]; - // flows[1]=tmp; - // } - // - // assert(tal_count(flows[0]->path) == 2); - // assert(tal_count(flows[0]->dirs) == 2); - // assert(tal_count(flows[0]->amounts) == 2); - - // assert(flows[0]->path[0] == gossmap_find_chan(gossmap, &scid12)); - // assert(flows[0]->path[1] == gossmap_find_chan(gossmap, &scid23)); - // assert(flows[0]->dirs[0] == 1); - // assert(flows[0]->dirs[1] == 0); - - // /* First one has probability ~ 50% */ - // assert(flows[0]->success_prob < 0.55); - // assert(flows[0]->success_prob > 0.45); - - // assert(tal_count(flows[1]->path) == 1); - // assert(tal_count(flows[1]->dirs) == 1); - // assert(tal_count(flows[1]->amounts) == 1); - - // /* We will try cheaper path first, but not to fill it! */ - // assert(flows[1]->path[0] == gossmap_find_chan(gossmap, &scid13)); - // assert(flows[1]->dirs[0] == 0); - // assert(amount_msat_less(flows[1]->amounts[0], AMOUNT_MSAT(400000000))); - - // /* Second one has probability ~ 50% */ - // assert(flows[1]->success_prob < 0.55); - // assert(flows[1]->success_prob > 0.45); - - // /* Delivered amount must be the total! */ - // assert(flows[0]->amounts[1].millisatoshis - // + flows[1]->amounts[0].millisatoshis == 500000000); - - // /* Clear them. */ - // remove_completed_flow(gossmap, chan_extra_map, flows[0]); - // remove_completed_flow(gossmap, chan_extra_map, flows[1]); - - printf("All set, let's call minflow ... \n"); - - /* Higher mu values mean we pay more for certainty! */ - struct flow **flows2 = minflow(tmpctx, gossmap, - gossmap_find_node(gossmap, &l1), - gossmap_find_node(gossmap, &l3), - uncertainty_get_chan_extra_map(uncertainty), - disabled, - /* This will go 400000000 via 1->3, rest via 1-2-3. */ - /* amount = */ AMOUNT_MSAT(500000000), //500k sats - /* max_fee = */ AMOUNT_MSAT(1000000), // 1k sats - /* min probability = */ 0.1, // 10% - /* base probability = */ 1.0, - /* delay fee factor = */ 1, - /* base fee penalty */ 1, - /* prob cost factor = */ 10, - &errmsg); - - printf("minflow completed execution.\n"); - - if (!flows) { - printf("Minflow has failed with: %s", errmsg); - assert(0 && "minflow failed"); - } - printf("%s\n", - print_flows(tmpctx,"Flow via two paths, high mu", gossmap, flows2)); - - printf("Verifying results ... \n"); - - assert(tal_count(flows2) == 2); - - /* The solution is composed by two paths, one with lenght 1 and the - * other with lenght 2. There is no guaranteed order of the solutions - * returning from minflow, hence we need to test them. */ - int ID1 = 0, ID2 = 1; - if(tal_count(flows2[ID1]->path)==2) - { - swap(&ID1,&ID2); - } - assert(tal_count(flows2[ID1]->path) == 1); - assert(tal_count(flows2[ID2]->path) == 2); - - // /* Sends more via 1->3, since it's more expensive (but lower prob) */ - assert(amount_msat_greater(flows2[ID1]->amount, flows2[ID2]->amount)); - assert(flows2[ID1]->success_prob < flows2[ID2]->success_prob); - - /* Delivered amount must be the total! */ - assert(flows2[ID1]->amount.millisatoshis - + flows2[ID2]->amount.millisatoshis == 500000000); - - common_shutdown(); -} diff --git a/plugins/renepay/test/run-missingcapacity.c b/plugins/renepay/test/run-missingcapacity.c deleted file mode 100644 index 4dc53ec4825f..000000000000 --- a/plugins/renepay/test/run-missingcapacity.c +++ /dev/null @@ -1,179 +0,0 @@ -/* 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 -#include -#include -#include -#include -#include - -/* AUTOGENERATED MOCKS START */ -/* Generated stub for sciddir_or_pubkey_from_node_id */ -bool sciddir_or_pubkey_from_node_id(struct sciddir_or_pubkey *sciddpk UNNEEDED, - const struct node_id *node_id UNNEEDED) -{ fprintf(stderr, "sciddir_or_pubkey_from_node_id called!\n"); abort(); } -/* AUTOGENERATED MOCKS END */ - -static u8 empty_map[] = {10}; - -#define NUM_NODES 4 - -static void remove_file(char *fname) { assert(!remove(fname)); } - -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); - tal_add_destructor(gossfile, remove_file); - 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.base_prob_success = 1.0; - 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(); -} - diff --git a/plugins/renepay/test/run-route_map.c b/plugins/renepay/test/run-route_map.c index 5dad081810a1..980f3d887fe7 100644 --- a/plugins/renepay/test/run-route_map.c +++ b/plugins/renepay/test/run-route_map.c @@ -17,7 +17,6 @@ #include #define RENEPAY_UNITTEST -#include "../flow.c" #include "../route.c" /* AUTOGENERATED MOCKS START */ diff --git a/plugins/renepay/test/run-testflow.c b/plugins/renepay/test/run-testflow.c deleted file mode 100644 index 5d907bae1547..000000000000 --- a/plugins/renepay/test/run-testflow.c +++ /dev/null @@ -1,842 +0,0 @@ -#include "config.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define MYLOG "/tmp/debug.txt" -#define RENEPAY_UNITTEST // logs are written in MYLOG -#include "../payment.c" -#include "../flow.c" -#include "../route.c" -#include "../uncertainty.c" -#include "../mcf.c" - -/* AUTOGENERATED MOCKS START */ -/* Generated stub for disabledmap_add_channel */ -void disabledmap_add_channel(struct disabledmap *p UNNEEDED, - struct short_channel_id_dir scidd UNNEEDED) -{ fprintf(stderr, "disabledmap_add_channel called!\n"); abort(); } -/* Generated stub for disabledmap_add_node */ -void disabledmap_add_node(struct disabledmap *p UNNEEDED, struct node_id node UNNEEDED) -{ fprintf(stderr, "disabledmap_add_node called!\n"); abort(); } -/* Generated stub for disabledmap_channel_is_warned */ -bool disabledmap_channel_is_warned(struct disabledmap *p UNNEEDED, - struct short_channel_id_dir scidd UNNEEDED) -{ fprintf(stderr, "disabledmap_channel_is_warned called!\n"); abort(); } -/* Generated stub for disabledmap_new */ -struct disabledmap *disabledmap_new(const tal_t *ctx UNNEEDED) -{ fprintf(stderr, "disabledmap_new called!\n"); abort(); } -/* Generated stub for disabledmap_reset */ -void disabledmap_reset(struct disabledmap *p UNNEEDED) -{ fprintf(stderr, "disabledmap_reset called!\n"); abort(); } -/* Generated stub for disabledmap_warn_channel */ -void disabledmap_warn_channel(struct disabledmap *p UNNEEDED, - struct short_channel_id_dir scidd UNNEEDED) -{ fprintf(stderr, "disabledmap_warn_channel called!\n"); abort(); } -/* Generated stub for json_add_payment */ -void json_add_payment(struct json_stream *s UNNEEDED, const struct payment *payment UNNEEDED) -{ fprintf(stderr, "json_add_payment called!\n"); abort(); } -/* Generated stub for new_routetracker */ -struct routetracker *new_routetracker(const tal_t *ctx UNNEEDED, struct payment *payment UNNEEDED) -{ fprintf(stderr, "new_routetracker called!\n"); abort(); } -/* Generated stub for pay_plugin */ -struct pay_plugin *pay_plugin; -/* Generated stub for routetracker_cleanup */ -void routetracker_cleanup(struct routetracker *routetracker UNNEEDED) -{ fprintf(stderr, "routetracker_cleanup called!\n"); abort(); } -/* Generated stub for sciddir_or_pubkey_from_node_id */ -bool sciddir_or_pubkey_from_node_id(struct sciddir_or_pubkey *sciddpk UNNEEDED, - const struct node_id *node_id UNNEEDED) -{ fprintf(stderr, "sciddir_or_pubkey_from_node_id called!\n"); abort(); } -/* AUTOGENERATED MOCKS END */ - -static const u8 canned_map[] = { -0x0c, 0x80, 0x00, 0x01, 0xbc, 0x86, 0xe4, 0xbf, 0x95, 0x00, 0x00, 0x00, 0x00, 0x10, 0x08, 0x00, -0x00, 0x00, 0x00, 0x00, 0x0f, 0x42, 0x40, 0x01, 0xb0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x22, 0x6e, -0x46, 0x11, 0x1a, 0x0b, 0x59, 0xca, 0xaf, 0x12, 0x60, 0x43, 0xeb, 0x5b, 0xbf, 0x28, 0xc3, 0x4f, -0x3a, 0x5e, 0x33, 0x2a, 0x1f, 0xc7, 0xb2, 0xb7, 0x3c, 0xf1, 0x88, 0x91, 0x0f, 0x00, 0x00, 0x71, -0x00, 0x00, 0x01, 0x00, 0x01, 0x02, 0x4f, 0x9d, 0xa0, 0xd7, 0x26, 0xad, 0xf0, 0xd9, 0xa4, 0xa3, -0xac, 0x32, 0xe3, 0x28, 0xb9, 0x3a, 0xd5, 0x27, 0xcc, 0xb9, 0xdb, 0x70, 0x77, 0xc5, 0x7a, 0x12, -0xc6, 0xf9, 0xfa, 0x9a, 0xdd, 0x74, 0x03, 0x7f, 0x97, 0xaf, 0x8e, 0x4f, 0xa6, 0x7c, 0x9b, 0x8d, -0x69, 0x70, 0x84, 0x95, 0x36, 0xf8, 0x88, 0xc3, 0x04, 0x31, 0x6d, 0x01, 0x5a, 0xf5, 0x12, 0x9e, -0x26, 0x8e, 0x01, 0x64, 0x96, 0x1b, 0x5e, 0x03, 0x1b, 0x84, 0xc5, 0x56, 0x7b, 0x12, 0x64, 0x40, -0x99, 0x5d, 0x3e, 0xd5, 0xaa, 0xba, 0x05, 0x65, 0xd7, 0x1e, 0x18, 0x34, 0x60, 0x48, 0x19, 0xff, -0x9c, 0x17, 0xf5, 0xe9, 0xd5, 0xdd, 0x07, 0x8f, 0x03, 0x1b, 0x84, 0xc5, 0x56, 0x7b, 0x12, 0x64, -0x40, 0x99, 0x5d, 0x3e, 0xd5, 0xaa, 0xba, 0x05, 0x65, 0xd7, 0x1e, 0x18, 0x34, 0x60, 0x48, 0x19, -0xff, 0x9c, 0x17, 0xf5, 0xe9, 0xd5, 0xdd, 0x07, 0x8f, 0x40, 0x00, 0x01, 0xb0, 0x24, 0x3a, 0xa3, -0x76, 0x64, 0x62, 0x19, 0xec, 0x01, 0x00, 0x66, 0x7f, 0x0f, 0xad, 0x6d, 0x9d, 0x58, 0x1b, 0x28, -0x8a, 0x67, 0x9d, 0xf8, 0xd1, 0x9d, 0x79, 0x4e, 0x67, 0xc8, 0x76, 0xbb, 0xdd, 0x4d, 0x8e, 0x45, -0x0d, 0xc9, 0x0e, 0x24, 0x76, 0xda, 0x44, 0x68, 0x7b, 0xe2, 0x14, 0xe8, 0x48, 0xfa, 0xd7, 0xc2, -0x35, 0xc5, 0x98, 0xd9, 0x7a, 0x6c, 0xcb, 0xb1, 0x4b, 0x19, 0xf9, 0xfa, 0xb2, 0x19, 0x3f, 0x87, -0xc1, 0xe9, 0x47, 0x51, 0x16, 0x64, 0x36, 0x2a, 0xeb, 0xc5, 0xaa, 0x20, 0x59, 0x4e, 0xdf, 0xae, -0x4e, 0x10, 0x38, 0x34, 0x8e, 0x06, 0x6e, 0x5d, 0x1b, 0x44, 0x30, 0xfb, 0x20, 0xed, 0xea, 0xde, -0x83, 0xcd, 0xa4, 0x8a, 0x5c, 0xad, 0x70, 0x2d, 0x8b, 0x04, 0xfb, 0xa2, 0xbd, 0x95, 0x7c, 0xdd, -0x66, 0xb5, 0x4e, 0xd6, 0xc6, 0x27, 0xdb, 0xa8, 0xe1, 0x26, 0x22, 0x81, 0x57, 0xe2, 0xaa, 0xe4, -0x82, 0xbe, 0x9e, 0x90, 0xc5, 0xc2, 0x59, 0x56, 0x9b, 0x79, 0xf3, 0xc3, 0xfe, 0x0c, 0xb3, 0x35, -0xeb, 0xba, 0xad, 0xf7, 0xd3, 0x24, 0x4e, 0x16, 0x15, 0x2d, 0x86, 0xd9, 0xe9, 0xd2, 0x38, 0x9b, -0xf9, 0xb3, 0x5f, 0x2c, 0x9b, 0xeb, 0xe0, 0x1c, 0xb3, 0xf0, 0x0f, 0xc1, 0x9d, 0x0b, 0x20, 0xa2, -0x19, 0xeb, 0x1a, 0x05, 0x8b, 0x8d, 0xb1, 0x22, 0x74, 0x7c, 0xa4, 0x39, 0x94, 0x6f, 0xfc, 0x34, -0x1b, 0xe5, 0x9f, 0x45, 0x8e, 0x12, 0x6e, 0x65, 0x73, 0x28, 0x21, 0x80, 0xfd, 0x9c, 0x0c, 0x89, -0x2b, 0xcb, 0x43, 0x2e, 0x7f, 0x47, 0xa1, 0xd7, 0x7e, 0xa9, 0xd7, 0x3e, 0xdd, 0xa0, 0xf8, 0x60, -0x9d, 0xde, 0x51, 0x3d, 0xc4, 0x21, 0x06, 0x61, 0xb3, 0x4d, 0xd8, 0x94, 0x4a, 0x3a, 0xc9, 0xb9, -0xc3, 0xcb, 0x09, 0xa3, 0x2f, 0x7b, 0x96, 0x53, 0x13, 0x1d, 0x6d, 0x7a, 0x28, 0xdd, 0xc8, 0x8d, -0xe4, 0x10, 0xad, 0x4c, 0xc6, 0xa0, 0x1b, 0x00, 0x00, 0x06, 0x22, 0x6e, 0x46, 0x11, 0x1a, 0x0b, -0x59, 0xca, 0xaf, 0x12, 0x60, 0x43, 0xeb, 0x5b, 0xbf, 0x28, 0xc3, 0x4f, 0x3a, 0x5e, 0x33, 0x2a, -0x1f, 0xc7, 0xb2, 0xb7, 0x3c, 0xf1, 0x88, 0x91, 0x0f, 0x00, 0x00, 0x71, 0x00, 0x00, 0x01, 0x00, -0x01, 0x02, 0x4f, 0x9d, 0xa0, 0xd7, 0x26, 0xad, 0xf0, 0xd9, 0xa4, 0xa3, 0xac, 0x32, 0xe3, 0x28, -0xb9, 0x3a, 0xd5, 0x27, 0xcc, 0xb9, 0xdb, 0x70, 0x77, 0xc5, 0x7a, 0x12, 0xc6, 0xf9, 0xfa, 0x9a, -0xdd, 0x74, 0x03, 0x7f, 0x97, 0xaf, 0x8e, 0x4f, 0xa6, 0x7c, 0x9b, 0x8d, 0x69, 0x70, 0x84, 0x95, -0x36, 0xf8, 0x88, 0xc3, 0x04, 0x31, 0x6d, 0x01, 0x5a, 0xf5, 0x12, 0x9e, 0x26, 0x8e, 0x01, 0x64, -0x96, 0x1b, 0x5e, 0x02, 0xca, 0x1a, 0xac, 0x5f, 0x7b, 0x86, 0x3a, 0x01, 0xc8, 0x69, 0x90, 0x82, -0xdf, 0x9a, 0x4d, 0xf8, 0x14, 0x0d, 0xd6, 0xe7, 0x10, 0x59, 0xd4, 0xec, 0x7f, 0x48, 0x13, 0xb0, -0x96, 0xb4, 0xa3, 0xad, 0x02, 0x21, 0x55, 0x92, 0x46, 0x1c, 0x84, 0x3d, 0x40, 0xe6, 0x01, 0x8d, -0x3d, 0x0c, 0xb6, 0xf4, 0xe1, 0x61, 0xe2, 0x4b, 0x59, 0x41, 0xdb, 0x3b, 0x20, 0x44, 0xbc, 0x0c, -0xb2, 0x0e, 0x4d, 0x3f, 0x9b, 0x00, 0x00, 0x00, 0x0a, 0x91, 0x11, 0x83, 0xf6, 0x00, 0x00, 0x00, -0x00, 0x10, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x42, 0x40, 0xc0, 0x00, 0x00, 0x8a, 0x01, -0x3d, 0x6f, 0x9a, 0x64, 0x62, 0x19, 0xec, 0x01, 0x02, 0x4c, 0x45, 0x7e, 0x21, 0xb8, 0xd5, 0x36, -0x98, 0xcd, 0x45, 0x03, 0x78, 0xa6, 0x51, 0xf1, 0xda, 0x1a, 0xb4, 0x46, 0xed, 0xfb, 0xed, 0x86, -0xf9, 0x31, 0x85, 0x2e, 0x3d, 0x80, 0x77, 0xf2, 0x13, 0x76, 0x91, 0x08, 0xe7, 0x52, 0x3d, 0xf4, -0xe5, 0x2e, 0x3b, 0x80, 0x2a, 0xbf, 0x54, 0xf8, 0x80, 0xbb, 0x77, 0x6f, 0xc6, 0xca, 0x9e, 0x3f, -0xe8, 0x96, 0xfa, 0x54, 0x7e, 0x94, 0x78, 0x0a, 0xec, 0x06, 0x22, 0x6e, 0x46, 0x11, 0x1a, 0x0b, -0x59, 0xca, 0xaf, 0x12, 0x60, 0x43, 0xeb, 0x5b, 0xbf, 0x28, 0xc3, 0x4f, 0x3a, 0x5e, 0x33, 0x2a, -0x1f, 0xc7, 0xb2, 0xb7, 0x3c, 0xf1, 0x88, 0x91, 0x0f, 0x00, 0x00, 0x71, 0x00, 0x00, 0x01, 0x00, -0x01, 0x64, 0x62, 0x19, 0xec, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x3b, 0x02, 0x33, -0x80, 0x40, 0x00, 0x00, 0xa4, 0x07, 0xd2, 0xf1, 0x5d, 0x64, 0x62, 0x19, 0xf1, 0x01, 0x01, 0x4d, -0xbe, 0x8a, 0xf5, 0xd8, 0x19, 0x2b, 0x99, 0xb0, 0xa0, 0xde, 0x24, 0x36, 0x32, 0x06, 0xac, 0x40, -0x4c, 0x41, 0x94, 0xc1, 0xd3, 0x85, 0xb5, 0xb8, 0x76, 0xbf, 0x98, 0xa9, 0x8e, 0xdb, 0xca, 0x43, -0x73, 0x98, 0xa0, 0xe0, 0x11, 0xa9, 0x95, 0xf3, 0xce, 0xde, 0xe5, 0x85, 0x80, 0x63, 0x8c, 0x12, -0x11, 0xee, 0xee, 0xa1, 0x3e, 0xcf, 0x4e, 0xd5, 0xae, 0x8d, 0x93, 0x22, 0xce, 0xbb, 0x02, 0x00, -0x07, 0x88, 0xa0, 0x80, 0x2a, 0x02, 0x69, 0xa2, 0x64, 0x62, 0x19, 0xf1, 0x02, 0x4f, 0x9d, 0xa0, -0xd7, 0x26, 0xad, 0xf0, 0xd9, 0xa4, 0xa3, 0xac, 0x32, 0xe3, 0x28, 0xb9, 0x3a, 0xd5, 0x27, 0xcc, -0xb9, 0xdb, 0x70, 0x77, 0xc5, 0x7a, 0x12, 0xc6, 0xf9, 0xfa, 0x9a, 0xdd, 0x74, 0x02, 0x4f, 0x9d, -0x4c, 0x4f, 0x55, 0x44, 0x54, 0x52, 0x41, 0x57, 0x4c, 0x2d, 0x2e, 0x30, 0x32, 0x2d, 0x33, 0x37, -0x2d, 0x67, 0x63, 0x39, 0x66, 0x38, 0x30, 0x33, 0x35, 0x2d, 0x6d, 0x6f, 0x64, 0x64, 0x65, 0x64, -0x00, 0x00, 0x01, 0x0d, 0x02, 0x9a, 0x00, 0x32, 0x00, 0x64, 0x00, 0x00, 0x00, 0x02, 0x4c, 0x4b, -0x40, 0xc0, 0x00, 0x00, 0x8a, 0x06, 0x22, 0xaa, 0xb5, 0x64, 0x62, 0x19, 0xec, 0x01, 0x02, 0x2b, -0x9e, 0x17, 0x25, 0x0f, 0x3d, 0x8c, 0x1c, 0x07, 0x6b, 0xb8, 0x7f, 0xdc, 0xc4, 0x30, 0xf4, 0xa7, -0xf8, 0x8b, 0x91, 0x53, 0xd6, 0xc1, 0x9d, 0x06, 0xb9, 0x18, 0xfb, 0xf0, 0x0b, 0x9a, 0x79, 0x2a, -0x56, 0x12, 0x35, 0x75, 0x4e, 0xf4, 0xb8, 0xb4, 0x2e, 0x72, 0x10, 0x3c, 0x8d, 0x76, 0x69, 0x1c, -0x67, 0xb0, 0x7f, 0x94, 0x07, 0xee, 0xb4, 0x38, 0x11, 0x0b, 0x7f, 0x62, 0x4e, 0x2a, 0x2d, 0x06, -0x22, 0x6e, 0x46, 0x11, 0x1a, 0x0b, 0x59, 0xca, 0xaf, 0x12, 0x60, 0x43, 0xeb, 0x5b, 0xbf, 0x28, -0xc3, 0x4f, 0x3a, 0x5e, 0x33, 0x2a, 0x1f, 0xc7, 0xb2, 0xb7, 0x3c, 0xf1, 0x88, 0x91, 0x0f, 0x00, -0x00, 0x71, 0x00, 0x00, 0x01, 0x00, 0x01, 0x64, 0x62, 0x19, 0xec, 0x01, 0x01, 0x00, 0x06, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, -0x00, 0x00, 0x00, 0x3b, 0x02, 0x33, 0x80, 0x00, 0x00, 0x00, 0xa4, 0xde, 0x6a, 0x84, 0x4d, 0x64, -0x62, 0x19, 0xf1, 0x01, 0x01, 0x47, 0x72, 0x62, 0xe8, 0xc7, 0x43, 0xa8, 0x2e, 0x1c, 0x97, 0x2a, -0x06, 0xce, 0x2f, 0xa2, 0xfa, 0x27, 0x4f, 0x28, 0x7f, 0x55, 0x32, 0x19, 0x62, 0x58, 0xc6, 0x18, -0x07, 0x23, 0x5f, 0x8a, 0x59, 0x00, 0x52, 0x4d, 0xc9, 0x18, 0x22, 0x9e, 0xf7, 0x87, 0xa3, 0x36, -0x9d, 0x01, 0x73, 0x7c, 0x5b, 0xb8, 0xb4, 0x08, 0x50, 0x0f, 0x89, 0x52, 0x3f, 0x2e, 0x44, 0xa0, -0xe0, 0x32, 0x3a, 0xf7, 0x20, 0x00, 0x07, 0x88, 0xa0, 0x80, 0x2a, 0x02, 0x69, 0xa2, 0x64, 0x62, -0x19, 0xf1, 0x03, 0x7f, 0x97, 0xaf, 0x8e, 0x4f, 0xa6, 0x7c, 0x9b, 0x8d, 0x69, 0x70, 0x84, 0x95, -0x36, 0xf8, 0x88, 0xc3, 0x04, 0x31, 0x6d, 0x01, 0x5a, 0xf5, 0x12, 0x9e, 0x26, 0x8e, 0x01, 0x64, -0x96, 0x1b, 0x5e, 0x03, 0x7f, 0x97, 0x53, 0x4c, 0x49, 0x43, 0x4b, 0x45, 0x52, 0x43, 0x48, 0x49, -0x50, 0x4d, 0x55, 0x4e, 0x4b, 0x2d, 0x2d, 0x67, 0x63, 0x39, 0x66, 0x38, 0x30, 0x33, 0x35, 0x2d, -0x6d, 0x6f, 0x64, 0x64, 0x65, 0x64, 0x00, 0x00, 0x01, 0x0d, 0x02, 0x9a, 0x00, 0x32, 0x00, 0x64, -0x00, 0x00, 0x00, 0x02, 0x4c, 0x4b, 0x40, 0x00, 0x00, 0x01, 0xb0, 0x31, 0xd6, 0x97, 0xf8, 0x64, -0x62, 0x19, 0xec, 0x01, 0x00, 0x3f, 0x22, 0x04, 0x81, 0x00, 0xfb, 0xfe, 0x52, 0x4e, 0xdf, 0x7e, -0xef, 0x65, 0xff, 0x41, 0xcf, 0xfc, 0x33, 0xfc, 0x27, 0xba, 0x5b, 0x5f, 0xc5, 0x40, 0xd7, 0xff, -0x65, 0x20, 0x37, 0x3f, 0x00, 0x0d, 0x7c, 0x9b, 0xa9, 0xf1, 0x8c, 0xc6, 0xf1, 0xf7, 0x30, 0xd8, -0x1a, 0x44, 0xea, 0x6a, 0xf8, 0x95, 0xde, 0xe9, 0x35, 0x5f, 0x2b, 0x09, 0xc8, 0x5e, 0xf4, 0xa4, -0x58, 0x5a, 0xef, 0x24, 0x14, 0x1e, 0x17, 0x5b, 0xb1, 0xa7, 0xbf, 0x69, 0xb6, 0x44, 0xbe, 0xcc, -0x37, 0xb3, 0x48, 0x0a, 0x83, 0x37, 0xfa, 0xdb, 0x1d, 0x2a, 0x57, 0x83, 0x50, 0x88, 0x39, 0xd7, -0x2d, 0xa6, 0x70, 0x19, 0x94, 0x63, 0xa3, 0x09, 0x57, 0x47, 0x80, 0x47, 0xa7, 0x9b, 0xb5, 0x20, -0x4a, 0x33, 0x67, 0xf7, 0x5c, 0x5d, 0x4c, 0xa3, 0xc3, 0x05, 0x81, 0x48, 0xa7, 0x5e, 0x10, 0x13, -0x5d, 0x64, 0x4c, 0x2e, 0x53, 0x28, 0xd1, 0x82, 0xc3, 0x7d, 0xbf, 0xb2, 0xcd, 0x36, 0xcc, 0x1e, -0xc6, 0xc7, 0x42, 0x65, 0x12, 0x61, 0x82, 0x5d, 0xc7, 0x3b, 0x6a, 0xaf, 0x71, 0xd4, 0xf0, 0xe9, -0xff, 0xdd, 0x75, 0x33, 0x96, 0x3e, 0xb7, 0x92, 0xc2, 0xcd, 0x0e, 0xda, 0xec, 0x55, 0x43, 0x20, -0x07, 0xe8, 0x9e, 0xff, 0x3f, 0xea, 0x2f, 0x44, 0x64, 0x43, 0xe9, 0xfd, 0x82, 0x0a, 0xd4, 0x1d, -0xf6, 0x14, 0x02, 0x30, 0x78, 0x34, 0x02, 0x62, 0x73, 0x90, 0x41, 0x38, 0xbe, 0xc0, 0xd2, 0xac, -0x59, 0xc1, 0x82, 0xd2, 0x6f, 0x4e, 0x28, 0xd9, 0x2e, 0x3c, 0x6d, 0x4b, 0xa2, 0x25, 0xc9, 0x46, -0x42, 0x95, 0x64, 0xb9, 0x89, 0x73, 0x30, 0xce, 0xb7, 0xca, 0x1a, 0x78, 0xac, 0xa8, 0x72, 0x71, -0xe8, 0x1e, 0x48, 0xe9, 0x7c, 0xe5, 0x49, 0x78, 0x16, 0x50, 0x3e, 0x26, 0x15, 0x4f, 0xaf, 0x7f, -0x53, 0x17, 0x14, 0xeb, 0xa6, 0x00, 0x00, 0x06, 0x22, 0x6e, 0x46, 0x11, 0x1a, 0x0b, 0x59, 0xca, -0xaf, 0x12, 0x60, 0x43, 0xeb, 0x5b, 0xbf, 0x28, 0xc3, 0x4f, 0x3a, 0x5e, 0x33, 0x2a, 0x1f, 0xc7, -0xb2, 0xb7, 0x3c, 0xf1, 0x88, 0x91, 0x0f, 0x00, 0x00, 0x71, 0x00, 0x00, 0x03, 0x00, 0x01, 0x02, -0x45, 0x1e, 0x9b, 0xaf, 0xf8, 0x1f, 0xaf, 0x5f, 0x41, 0x0b, 0x0b, 0xbe, 0x9e, 0x36, 0xee, 0x83, -0xbf, 0x8f, 0x79, 0x69, 0x8e, 0x66, 0x05, 0xa5, 0x1a, 0x97, 0x48, 0xbc, 0x73, 0xc7, 0xdc, 0x28, -0x03, 0x7f, 0x97, 0xaf, 0x8e, 0x4f, 0xa6, 0x7c, 0x9b, 0x8d, 0x69, 0x70, 0x84, 0x95, 0x36, 0xf8, -0x88, 0xc3, 0x04, 0x31, 0x6d, 0x01, 0x5a, 0xf5, 0x12, 0x9e, 0x26, 0x8e, 0x01, 0x64, 0x96, 0x1b, -0x5e, 0x02, 0xd0, 0xbf, 0x3d, 0xe0, 0x25, 0x7a, 0xe4, 0x02, 0x4a, 0x88, 0x2b, 0x20, 0x63, 0xb4, -0x68, 0x6b, 0x72, 0x27, 0x91, 0xc2, 0xe4, 0x7a, 0xd1, 0x75, 0x93, 0x5d, 0xf3, 0x3a, 0xbe, 0x99, -0x7b, 0x76, 0x02, 0x49, 0x4d, 0xb8, 0x75, 0x3e, 0x66, 0xc7, 0x73, 0x63, 0xec, 0xf4, 0x40, 0xa4, -0xcb, 0xe5, 0xe0, 0x3e, 0xc6, 0x28, 0x2b, 0xea, 0x8a, 0xd3, 0x3f, 0x66, 0x4b, 0xa3, 0x9b, 0x86, -0x37, 0xf7, 0x7b, 0x00, 0x00, 0x00, 0x0a, 0xea, 0x64, 0x27, 0x09, 0x00, 0x00, 0x00, 0x00, 0x10, -0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x84, 0x80, 0x80, 0x00, 0x00, 0x8a, 0x67, 0x5f, 0xf2, -0xad, 0x64, 0x62, 0x19, 0xec, 0x01, 0x02, 0x28, 0x06, 0xbe, 0x81, 0x8d, 0x13, 0xe3, 0xe9, 0x45, -0x09, 0xdd, 0x6a, 0xbe, 0x96, 0xb5, 0x08, 0xe4, 0x87, 0xca, 0xfd, 0x72, 0xc1, 0xfd, 0xa9, 0xe8, -0x32, 0x68, 0x95, 0x97, 0x06, 0x47, 0x57, 0x3a, 0x38, 0x28, 0x22, 0xa1, 0x78, 0x45, 0x22, 0xd5, -0xac, 0x0d, 0x1d, 0x2f, 0x25, 0xf0, 0x3a, 0x11, 0x85, 0x34, 0xcc, 0xae, 0xf8, 0xdd, 0x44, 0x05, -0xdd, 0xe6, 0x6d, 0xfc, 0xc2, 0xa0, 0x7e, 0x06, 0x22, 0x6e, 0x46, 0x11, 0x1a, 0x0b, 0x59, 0xca, -0xaf, 0x12, 0x60, 0x43, 0xeb, 0x5b, 0xbf, 0x28, 0xc3, 0x4f, 0x3a, 0x5e, 0x33, 0x2a, 0x1f, 0xc7, -0xb2, 0xb7, 0x3c, 0xf1, 0x88, 0x91, 0x0f, 0x00, 0x00, 0x71, 0x00, 0x00, 0x03, 0x00, 0x01, 0x64, -0x62, 0x19, 0xec, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x76, 0x04, 0x67, 0x00, 0x80, -0x00, 0x00, 0x8a, 0xdc, 0x8e, 0xb4, 0xa3, 0x64, 0x62, 0x19, 0xec, 0x01, 0x02, 0x27, 0x9a, 0x87, -0xb6, 0x8b, 0xcb, 0xc9, 0x41, 0xea, 0xc3, 0x1b, 0x18, 0xf5, 0x51, 0x2f, 0x9b, 0x71, 0xe3, 0x8d, -0x24, 0x8d, 0x1e, 0x53, 0xdc, 0x83, 0x6f, 0x30, 0xfe, 0x00, 0xeb, 0xbb, 0x6b, 0x35, 0xc3, 0x20, -0xea, 0xae, 0x27, 0xb4, 0x8a, 0xdc, 0x30, 0x9f, 0xb5, 0xee, 0xbf, 0x3c, 0x16, 0x58, 0xe1, 0xa6, -0xec, 0x87, 0xfd, 0xb0, 0x43, 0x8c, 0xed, 0x4d, 0x00, 0x2d, 0x85, 0x33, 0xbe, 0x06, 0x22, 0x6e, -0x46, 0x11, 0x1a, 0x0b, 0x59, 0xca, 0xaf, 0x12, 0x60, 0x43, 0xeb, 0x5b, 0xbf, 0x28, 0xc3, 0x4f, -0x3a, 0x5e, 0x33, 0x2a, 0x1f, 0xc7, 0xb2, 0xb7, 0x3c, 0xf1, 0x88, 0x91, 0x0f, 0x00, 0x00, 0x71, -0x00, 0x00, 0x03, 0x00, 0x01, 0x64, 0x62, 0x19, 0xec, 0x01, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, -0x00, 0x76, 0x04, 0x67, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x1e, 0xb8, 0x7e, 0x0a, 0x64, 0x62, 0x19, -0xf1, 0x01, 0x01, 0x70, 0xc5, 0x12, 0xaa, 0x59, 0xee, 0xe5, 0xb5, 0x1f, 0x4c, 0x56, 0x77, 0xa1, -0xc5, 0x3c, 0x6b, 0x03, 0x37, 0xf9, 0x8f, 0xa9, 0x50, 0xa7, 0xe3, 0x22, 0x7b, 0x6e, 0x37, 0xd5, -0x46, 0x03, 0xff, 0x12, 0x91, 0x0a, 0xb8, 0x4f, 0x35, 0x63, 0xdf, 0xda, 0x03, 0xda, 0xee, 0x86, -0xe4, 0x43, 0xef, 0xa0, 0x8a, 0x90, 0xeb, 0xa8, 0xf3, 0x7f, 0x05, 0x84, 0x8a, 0xd8, 0xb0, 0xf8, -0x1b, 0x4b, 0xcf, 0x00, 0x07, 0x88, 0xa0, 0x80, 0x2a, 0x02, 0x69, 0xa2, 0x64, 0x62, 0x19, 0xf1, -0x02, 0x45, 0x1e, 0x9b, 0xaf, 0xf8, 0x1f, 0xaf, 0x5f, 0x41, 0x0b, 0x0b, 0xbe, 0x9e, 0x36, 0xee, -0x83, 0xbf, 0x8f, 0x79, 0x69, 0x8e, 0x66, 0x05, 0xa5, 0x1a, 0x97, 0x48, 0xbc, 0x73, 0xc7, 0xdc, -0x28, 0x02, 0x45, 0x1e, 0x4c, 0x4f, 0x55, 0x44, 0x54, 0x4f, 0x54, 0x45, 0x2d, 0x33, 0x2e, 0x30, -0x32, 0x2d, 0x33, 0x37, 0x2d, 0x67, 0x63, 0x39, 0x66, 0x38, 0x30, 0x33, 0x35, 0x2d, 0x6d, 0x6f, -0x64, 0x64, 0x65, 0x64, 0x00, 0x00, 0x01, 0x0d, 0x02, 0x9a, 0x00, 0x32, 0x00, 0x64, 0x00, 0x00, -0x00, 0x02, 0x4c, 0x4b, 0x40, 0x00, 0x00, 0x01, 0xb0, 0x17, 0xe0, 0xd7, 0x83, 0x64, 0x62, 0x19, -0xed, 0x01, 0x00, 0x48, 0xa3, 0x33, 0x5f, 0x33, 0x6c, 0x33, 0x85, 0x0f, 0xc7, 0xeb, 0x46, 0x04, -0x5a, 0xe7, 0x1a, 0x2d, 0xe1, 0x37, 0xb0, 0xc3, 0x8a, 0xa7, 0x6a, 0xe0, 0xa2, 0xfd, 0x1f, 0x30, -0x9f, 0xdc, 0x8d, 0x38, 0x05, 0xf7, 0xaf, 0x0b, 0xe6, 0xb3, 0x4d, 0x62, 0xb9, 0xa4, 0x9c, 0x53, -0x7d, 0x6e, 0x59, 0x5b, 0xb2, 0x2b, 0x5c, 0xda, 0x35, 0xf9, 0x90, 0x63, 0x21, 0xa8, 0xb1, 0x53, -0xc3, 0x35, 0x7c, 0x36, 0x76, 0x21, 0x76, 0xae, 0xa3, 0xad, 0x05, 0x53, 0xa7, 0xbd, 0x9d, 0x38, -0x54, 0x03, 0xc0, 0x98, 0x1d, 0x66, 0xc1, 0x04, 0x39, 0xc1, 0x88, 0xd1, 0x1f, 0x90, 0x08, 0x96, -0xbc, 0x59, 0x54, 0x4f, 0x5f, 0xa2, 0x70, 0xcd, 0xf0, 0xda, 0x96, 0x3c, 0x51, 0x04, 0x67, 0x5c, -0x1f, 0x07, 0xed, 0xf9, 0x9e, 0x98, 0xd0, 0x3b, 0x5e, 0x51, 0xa9, 0xa6, 0x82, 0xc1, 0xed, 0x35, -0x45, 0xa1, 0xd6, 0x36, 0x3b, 0xa1, 0xe6, 0x5d, 0x1f, 0xec, 0xe2, 0xb7, 0xf8, 0xa2, 0xe4, 0x45, -0xf9, 0xb6, 0xa7, 0x07, 0x18, 0xc7, 0xb5, 0x0c, 0x08, 0xd7, 0x50, 0x36, 0x98, 0x82, 0xd3, 0xc8, -0x40, 0xc8, 0xdc, 0x64, 0x27, 0xe2, 0x14, 0x42, 0x44, 0x0a, 0xe4, 0x1d, 0x41, 0x61, 0x57, 0x88, -0xfe, 0xd2, 0x51, 0x99, 0x24, 0x55, 0x1e, 0x3b, 0xaa, 0x8d, 0xa7, 0xb4, 0xc0, 0x6e, 0xf5, 0x70, -0x8c, 0x2a, 0xe3, 0x75, 0xcc, 0x36, 0xbf, 0xbe, 0xfc, 0x3f, 0x09, 0x83, 0x5e, 0xe4, 0x20, 0x9a, -0xcc, 0x11, 0x48, 0x8e, 0x2b, 0xc8, 0x8a, 0xef, 0xc0, 0x78, 0x45, 0xee, 0x1e, 0xc7, 0xce, 0x00, -0xfc, 0x3c, 0x0e, 0x32, 0xd2, 0x8f, 0x15, 0x8c, 0x02, 0xb3, 0x7b, 0x4c, 0xa9, 0x7a, 0x9c, 0xec, -0x5e, 0x6e, 0xf2, 0xd3, 0xd9, 0x15, 0x32, 0xa3, 0x74, 0x14, 0xbf, 0x1f, 0xdd, 0x2f, 0x63, 0x3c, -0x47, 0x04, 0x6c, 0x00, 0x00, 0x06, 0x22, 0x6e, 0x46, 0x11, 0x1a, 0x0b, 0x59, 0xca, 0xaf, 0x12, -0x60, 0x43, 0xeb, 0x5b, 0xbf, 0x28, 0xc3, 0x4f, 0x3a, 0x5e, 0x33, 0x2a, 0x1f, 0xc7, 0xb2, 0xb7, -0x3c, 0xf1, 0x88, 0x91, 0x0f, 0x00, 0x00, 0x71, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x45, 0x1e, -0x9b, 0xaf, 0xf8, 0x1f, 0xaf, 0x5f, 0x41, 0x0b, 0x0b, 0xbe, 0x9e, 0x36, 0xee, 0x83, 0xbf, 0x8f, -0x79, 0x69, 0x8e, 0x66, 0x05, 0xa5, 0x1a, 0x97, 0x48, 0xbc, 0x73, 0xc7, 0xdc, 0x28, 0x02, 0xd1, -0xab, 0x24, 0xfe, 0x53, 0xcf, 0xca, 0xc4, 0xa4, 0x77, 0x74, 0x52, 0x35, 0xc7, 0xac, 0x3e, 0x75, -0x16, 0x1a, 0x5b, 0xf8, 0x42, 0x6e, 0xa8, 0xe0, 0x18, 0x2a, 0xd5, 0x8c, 0xab, 0x36, 0x7f, 0x02, -0x7e, 0x2a, 0xc0, 0xec, 0x93, 0xfd, 0xb3, 0xfb, 0xe3, 0x8d, 0x7a, 0x3f, 0x5e, 0xa0, 0xa6, 0x3d, -0xdb, 0xa9, 0x8a, 0x51, 0xb7, 0x7a, 0xf5, 0x51, 0x6f, 0xe5, 0xca, 0x10, 0x10, 0xd7, 0x95, 0x34, -0x02, 0x17, 0xd5, 0xb1, 0x80, 0x7d, 0x8b, 0x95, 0x7c, 0xe1, 0x0b, 0xb0, 0xaf, 0xf3, 0xc1, 0x84, -0x81, 0xee, 0x2f, 0xed, 0x6a, 0x7b, 0x65, 0x9c, 0xbf, 0xfd, 0x48, 0x20, 0xd0, 0x9d, 0x1a, 0xfd, -0xa4, 0x00, 0x00, 0x00, 0x0a, 0x91, 0x11, 0x83, 0xf6, 0x00, 0x00, 0x00, 0x00, 0x10, 0x05, 0x00, -0x00, 0x00, 0x00, 0x00, 0x0f, 0x42, 0x40, 0x80, 0x00, 0x00, 0x8a, 0xbd, 0x52, 0xa0, 0x78, 0x64, -0x62, 0x19, 0xed, 0x01, 0x02, 0x40, 0xf0, 0x06, 0x07, 0x97, 0xb8, 0x87, 0xef, 0x73, 0xdc, 0x1b, -0xf0, 0x20, 0x31, 0x55, 0xc9, 0xb9, 0x6f, 0xec, 0x6f, 0xad, 0x46, 0x86, 0x0a, 0xcc, 0xd9, 0x95, -0x61, 0x62, 0x15, 0x84, 0x70, 0x2a, 0x47, 0xd7, 0x68, 0xa9, 0xbc, 0x98, 0xb3, 0x1f, 0xc4, 0xbc, -0x78, 0xab, 0x5d, 0xf2, 0xf7, 0xc4, 0x97, 0x75, 0x21, 0x13, 0xcf, 0xfc, 0xd4, 0x36, 0xcd, 0xf6, -0xb4, 0x85, 0x7c, 0xad, 0x01, 0x06, 0x22, 0x6e, 0x46, 0x11, 0x1a, 0x0b, 0x59, 0xca, 0xaf, 0x12, -0x60, 0x43, 0xeb, 0x5b, 0xbf, 0x28, 0xc3, 0x4f, 0x3a, 0x5e, 0x33, 0x2a, 0x1f, 0xc7, 0xb2, 0xb7, -0x3c, 0xf1, 0x88, 0x91, 0x0f, 0x00, 0x00, 0x71, 0x00, 0x00, 0x02, 0x00, 0x00, 0x64, 0x62, 0x19, -0xed, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x3b, 0x02, 0x33, 0x80, 0x80, 0x00, 0x00, -0x8a, 0xf5, 0x5d, 0xd1, 0x12, 0x64, 0x62, 0x19, 0xed, 0x01, 0x02, 0x08, 0x97, 0x08, 0x72, 0xbe, -0xc8, 0x1e, 0xd0, 0xb9, 0xb8, 0x4b, 0x0f, 0x63, 0x5c, 0xeb, 0x28, 0xa5, 0xf8, 0x7a, 0x3d, 0xa1, -0x6a, 0xb3, 0xb4, 0x30, 0x91, 0x31, 0x57, 0xd4, 0x5b, 0x69, 0x26, 0x4d, 0xd1, 0xbb, 0xd5, 0x49, -0x95, 0xe9, 0x75, 0x53, 0xa4, 0xae, 0x87, 0xe9, 0x88, 0xf6, 0x86, 0x1f, 0x31, 0x8f, 0x35, 0xf9, -0x15, 0xcc, 0x04, 0x0a, 0x01, 0xed, 0x6e, 0x47, 0xe0, 0xea, 0x68, 0x06, 0x22, 0x6e, 0x46, 0x11, -0x1a, 0x0b, 0x59, 0xca, 0xaf, 0x12, 0x60, 0x43, 0xeb, 0x5b, 0xbf, 0x28, 0xc3, 0x4f, 0x3a, 0x5e, -0x33, 0x2a, 0x1f, 0xc7, 0xb2, 0xb7, 0x3c, 0xf1, 0x88, 0x91, 0x0f, 0x00, 0x00, 0x71, 0x00, 0x00, -0x02, 0x00, 0x00, 0x64, 0x62, 0x19, 0xed, 0x01, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x3b, -0x02, 0x33, 0x80, 0x00, 0x00, 0x00, 0xa4, 0x07, 0x3d, 0xb7, 0xfe, 0x64, 0x62, 0x19, 0xf2, 0x01, -0x01, 0x29, 0x2a, 0x41, 0x8f, 0xb7, 0x24, 0xc2, 0x82, 0xc5, 0x75, 0x0e, 0x28, 0xd9, 0x8b, 0xd4, -0xad, 0xa1, 0xb1, 0x9a, 0x65, 0xa8, 0x7a, 0x78, 0xc7, 0x6c, 0xc8, 0x94, 0xcb, 0xf7, 0xb1, 0xb8, -0x3b, 0x29, 0xce, 0xbf, 0xcc, 0x47, 0x1b, 0x5a, 0xb4, 0xec, 0xab, 0xa3, 0xbe, 0xaf, 0xd1, 0xde, -0xd7, 0x0e, 0x8b, 0xcc, 0xaa, 0xdb, 0x6b, 0x88, 0x51, 0xb9, 0x7a, 0x0c, 0xcd, 0x1c, 0x9c, 0x4d, -0x5c, 0x00, 0x07, 0x88, 0xa0, 0x80, 0x2a, 0x02, 0x69, 0xa2, 0x64, 0x62, 0x19, 0xf2, 0x02, 0xd1, -0xab, 0x24, 0xfe, 0x53, 0xcf, 0xca, 0xc4, 0xa4, 0x77, 0x74, 0x52, 0x35, 0xc7, 0xac, 0x3e, 0x75, -0x16, 0x1a, 0x5b, 0xf8, 0x42, 0x6e, 0xa8, 0xe0, 0x18, 0x2a, 0xd5, 0x8c, 0xab, 0x36, 0x7f, 0x02, -0xd1, 0xab, 0x43, 0x48, 0x49, 0x4c, 0x4c, 0x59, 0x46, 0x49, 0x52, 0x45, 0x2d, 0x30, 0x32, 0x2d, -0x33, 0x37, 0x2d, 0x67, 0x63, 0x39, 0x66, 0x38, 0x30, 0x33, 0x35, 0x2d, 0x6d, 0x6f, 0x64, 0x64, -0x65, 0x64, 0x00, 0x00, 0x01, 0x0d, 0x02, 0x9a, 0x00, 0x32, 0x00, 0x64, 0x00, 0x00, 0x00, 0x02, -0x4c, 0x4b, 0x40, 0x00, 0x00, 0x01, 0xb0, 0x5f, 0xad, 0x58, 0xa1, 0x64, 0x62, 0x19, 0xed, 0x01, -0x00, 0x63, 0x42, 0xba, 0xf1, 0x21, 0xe0, 0x09, 0x57, 0x0d, 0x40, 0xa4, 0xc6, 0x05, 0x78, 0x02, -0x8e, 0x35, 0x71, 0x66, 0x7c, 0x24, 0x51, 0x3f, 0x58, 0x3a, 0xaa, 0x14, 0x65, 0x5a, 0x2b, 0xbd, -0x09, 0x5b, 0xd3, 0xa8, 0x4e, 0x73, 0x3e, 0x38, 0xd3, 0x4d, 0x19, 0x9c, 0x18, 0x04, 0x60, 0x57, -0x32, 0xd3, 0x75, 0xf5, 0x36, 0x15, 0xc4, 0x6a, 0xf1, 0x1b, 0x3d, 0xc9, 0x07, 0x89, 0x08, 0x7a, -0x37, 0x2f, 0xf1, 0x89, 0x12, 0xdb, 0xf2, 0xff, 0x04, 0xbd, 0x93, 0x23, 0x00, 0x3c, 0x10, 0x05, -0x8a, 0x58, 0x9b, 0x96, 0xf3, 0x76, 0x94, 0x16, 0x29, 0x51, 0xc8, 0x76, 0x89, 0x61, 0xc3, 0x21, -0xc0, 0x0e, 0x47, 0xac, 0xa3, 0xbe, 0xc7, 0xfd, 0xa2, 0x6b, 0xe9, 0x1d, 0xe2, 0x11, 0x1c, 0x3e, -0xfc, 0x6d, 0x4d, 0x0b, 0x85, 0xff, 0xe9, 0x8a, 0x39, 0x3a, 0xb3, 0x0e, 0x2f, 0x28, 0x96, 0x6b, -0x96, 0x59, 0x4d, 0x53, 0x71, 0xd5, 0x38, 0x23, 0xe1, 0xe0, 0xad, 0x0a, 0xbf, 0x00, 0x58, 0x15, -0xbf, 0x53, 0x07, 0xe1, 0x13, 0x06, 0x88, 0xb3, 0xf8, 0x31, 0x06, 0x72, 0x92, 0x6f, 0xd1, 0xf0, -0x9b, 0x3b, 0xf2, 0x8f, 0x9c, 0xc6, 0x73, 0xf8, 0x91, 0x3e, 0x84, 0xc0, 0xed, 0xdf, 0x92, 0x43, -0x92, 0x5f, 0x4a, 0x6b, 0x96, 0x02, 0xaf, 0xd9, 0xd9, 0xd9, 0xf9, 0x65, 0xae, 0x08, 0xd8, 0x62, -0x93, 0x2b, 0xb7, 0xd3, 0x48, 0xe3, 0x02, 0x19, 0x53, 0xf9, 0x49, 0x24, 0xfa, 0x22, 0x24, 0x87, -0xc2, 0xd2, 0x0b, 0xc0, 0x56, 0xae, 0x09, 0x5a, 0x94, 0xc3, 0x54, 0x59, 0xb5, 0xe7, 0xbe, 0xa6, -0x4a, 0x47, 0xc1, 0x79, 0x80, 0xe8, 0xc2, 0xd1, 0xc5, 0xda, 0x6b, 0x25, 0x85, 0xc6, 0x02, 0x32, -0x8b, 0x52, 0x0e, 0x7f, 0x18, 0x1c, 0x5b, 0xf6, 0xb9, 0xaf, 0x69, 0xdc, 0xc6, 0x3d, 0x93, 0xc1, -0x27, 0x00, 0x00, 0x06, 0x22, 0x6e, 0x46, 0x11, 0x1a, 0x0b, 0x59, 0xca, 0xaf, 0x12, 0x60, 0x43, -0xeb, 0x5b, 0xbf, 0x28, 0xc3, 0x4f, 0x3a, 0x5e, 0x33, 0x2a, 0x1f, 0xc7, 0xb2, 0xb7, 0x3c, 0xf1, -0x88, 0x91, 0x0f, 0x00, 0x00, 0x71, 0x00, 0x00, 0x04, 0x00, 0x01, 0x02, 0xd1, 0xab, 0x24, 0xfe, -0x53, 0xcf, 0xca, 0xc4, 0xa4, 0x77, 0x74, 0x52, 0x35, 0xc7, 0xac, 0x3e, 0x75, 0x16, 0x1a, 0x5b, -0xf8, 0x42, 0x6e, 0xa8, 0xe0, 0x18, 0x2a, 0xd5, 0x8c, 0xab, 0x36, 0x7f, 0x03, 0xca, 0xec, 0x54, -0x08, 0x55, 0x08, 0xda, 0x06, 0xf1, 0x4f, 0xfb, 0x83, 0x61, 0x66, 0xca, 0x22, 0x04, 0x2d, 0x0f, -0xda, 0x69, 0x69, 0x03, 0x56, 0x23, 0x2a, 0x24, 0xb8, 0x36, 0x6c, 0x8f, 0x85, 0x03, 0xf6, 0x19, -0x62, 0x15, 0xf2, 0x5c, 0xfc, 0x5c, 0xae, 0x8c, 0xb6, 0x90, 0xa7, 0x81, 0xe0, 0x14, 0xb5, 0xc1, -0xc5, 0xda, 0xf9, 0x6d, 0x44, 0x6d, 0x1a, 0x6e, 0x24, 0x4f, 0xb6, 0x42, 0x3f, 0xdb, 0x03, 0xf9, -0x84, 0xe3, 0xec, 0xa9, 0x24, 0x5d, 0x1b, 0xba, 0xd2, 0xc7, 0xf3, 0x5a, 0x32, 0xaa, 0x6e, 0xdb, -0x21, 0xb6, 0xe8, 0xb1, 0x86, 0x5b, 0x18, 0x30, 0xe8, 0x4d, 0x23, 0xa4, 0x45, 0x23, 0x88, 0x00, -0x00, 0x00, 0x0a, 0x08, 0x85, 0x8a, 0xb2, 0x00, 0x00, 0x00, 0x00, 0x10, 0x05, 0x00, 0x00, 0x00, -0x00, 0x00, 0x2d, 0xc6, 0xc0, 0x80, 0x00, 0x00, 0x8a, 0xe9, 0x51, 0x74, 0x9b, 0x64, 0x62, 0x19, -0xed, 0x01, 0x02, 0x4b, 0x82, 0x87, 0x3b, 0xc9, 0x03, 0x1c, 0x6e, 0xc9, 0xbe, 0x96, 0x22, 0x97, -0xf7, 0xa8, 0xb0, 0xb2, 0x7c, 0x22, 0x69, 0x23, 0x2d, 0x97, 0xfb, 0x9b, 0xc2, 0xf1, 0x1e, 0x66, -0xfb, 0xfd, 0x80, 0x5d, 0xd7, 0xf0, 0x23, 0x31, 0x47, 0xaa, 0x54, 0x8d, 0x95, 0xbb, 0xdd, 0x33, -0x13, 0x32, 0x6d, 0x91, 0xc6, 0x45, 0xd5, 0x84, 0xf4, 0x76, 0x6c, 0x74, 0xf3, 0x51, 0x45, 0x24, -0xee, 0x5b, 0xc3, 0x06, 0x22, 0x6e, 0x46, 0x11, 0x1a, 0x0b, 0x59, 0xca, 0xaf, 0x12, 0x60, 0x43, -0xeb, 0x5b, 0xbf, 0x28, 0xc3, 0x4f, 0x3a, 0x5e, 0x33, 0x2a, 0x1f, 0xc7, 0xb2, 0xb7, 0x3c, 0xf1, -0x88, 0x91, 0x0f, 0x00, 0x00, 0x71, 0x00, 0x00, 0x04, 0x00, 0x01, 0x64, 0x62, 0x19, 0xed, 0x01, -0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, -0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xb1, 0x06, 0x9a, 0x80, 0x80, 0x00, 0x00, 0x8a, 0xd2, -0xc8, 0xd2, 0x7c, 0x64, 0x62, 0x19, 0xed, 0x01, 0x02, 0x0f, 0x90, 0xcb, 0xb6, 0xa1, 0x44, 0x65, -0x10, 0x00, 0xae, 0x2f, 0x60, 0x26, 0x2f, 0x41, 0x58, 0x5b, 0xab, 0xde, 0xff, 0x7e, 0x11, 0x44, -0xf4, 0x2e, 0x96, 0x96, 0xfa, 0x98, 0x09, 0xee, 0xb1, 0x5d, 0x43, 0xff, 0x44, 0x7b, 0xa6, 0x03, -0xf6, 0x4a, 0x07, 0x38, 0x97, 0x59, 0xee, 0x5e, 0xee, 0xcb, 0xdb, 0x77, 0x69, 0xab, 0x61, 0xd8, -0xc3, 0x42, 0xb3, 0x1f, 0x57, 0xea, 0xf3, 0xfd, 0xe2, 0x06, 0x22, 0x6e, 0x46, 0x11, 0x1a, 0x0b, -0x59, 0xca, 0xaf, 0x12, 0x60, 0x43, 0xeb, 0x5b, 0xbf, 0x28, 0xc3, 0x4f, 0x3a, 0x5e, 0x33, 0x2a, -0x1f, 0xc7, 0xb2, 0xb7, 0x3c, 0xf1, 0x88, 0x91, 0x0f, 0x00, 0x00, 0x71, 0x00, 0x00, 0x04, 0x00, -0x01, 0x64, 0x62, 0x19, 0xed, 0x01, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xb1, 0x06, 0x9a, -0x80, 0x40, 0x00, 0x00, 0x8a, 0x64, 0xa7, 0x4f, 0x57, 0x64, 0x62, 0x19, 0xf7, 0x01, 0x02, 0x28, -0x15, 0x9f, 0xa7, 0x51, 0x3a, 0xbb, 0x33, 0xd9, 0x25, 0xaa, 0x7d, 0xe8, 0xfb, 0x3a, 0x92, 0x45, -0x41, 0xb3, 0x22, 0x9b, 0x12, 0x3b, 0xb0, 0x16, 0x47, 0xd6, 0xf7, 0x61, 0x44, 0x1d, 0xa7, 0x35, -0xfe, 0xa9, 0x7b, 0xa6, 0x42, 0x91, 0x3f, 0x5e, 0xe4, 0xca, 0x98, 0x1c, 0x0f, 0x2d, 0xed, 0x36, -0x0e, 0x2b, 0x2e, 0x08, 0x81, 0x2e, 0xcc, 0xc1, 0x76, 0x61, 0xf9, 0x1b, 0xd3, 0x44, 0x3e, 0x06, -0x22, 0x6e, 0x46, 0x11, 0x1a, 0x0b, 0x59, 0xca, 0xaf, 0x12, 0x60, 0x43, 0xeb, 0x5b, 0xbf, 0x28, -0xc3, 0x4f, 0x3a, 0x5e, 0x33, 0x2a, 0x1f, 0xc7, 0xb2, 0xb7, 0x3c, 0xf1, 0x88, 0x91, 0x0f, 0x00, -0x00, 0x71, 0x00, 0x00, 0x01, 0x00, 0x01, 0x64, 0x62, 0x19, 0xf7, 0x01, 0x00, 0x00, 0x06, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x64, 0x00, -0x00, 0x00, 0x00, 0x3b, 0x02, 0x33, 0x80, 0x00, 0x00, 0x00, 0xa4, 0x30, 0x77, 0x80, 0xee, 0x64, -0x62, 0x19, 0xf2, 0x01, 0x01, 0x67, 0x07, 0x1d, 0x3b, 0x62, 0x2d, 0xb7, 0x1a, 0xba, 0xb8, 0x93, -0x56, 0xaa, 0xfa, 0xb1, 0x47, 0x4f, 0x0e, 0x02, 0x8b, 0x73, 0xd5, 0x5b, 0xce, 0xd6, 0x40, 0x55, -0xaf, 0xa7, 0x29, 0xd0, 0x51, 0x24, 0x5a, 0x19, 0x22, 0xc6, 0x7b, 0x6e, 0x4a, 0xae, 0x57, 0x9c, -0x16, 0x99, 0x46, 0x6c, 0xc3, 0x64, 0xd8, 0x20, 0x10, 0x44, 0x1e, 0xd0, 0x6b, 0x8d, 0x36, 0xdc, -0xae, 0x75, 0x06, 0x6e, 0xdc, 0x00, 0x07, 0x88, 0xa0, 0x80, 0x2a, 0x02, 0x69, 0xa2, 0x64, 0x62, -0x19, 0xf2, 0x03, 0xca, 0xec, 0x54, 0x08, 0x55, 0x08, 0xda, 0x06, 0xf1, 0x4f, 0xfb, 0x83, 0x61, -0x66, 0xca, 0x22, 0x04, 0x2d, 0x0f, 0xda, 0x69, 0x69, 0x03, 0x56, 0x23, 0x2a, 0x24, 0xb8, 0x36, -0x6c, 0x8f, 0x85, 0x03, 0xca, 0xec, 0x56, 0x49, 0x4f, 0x4c, 0x45, 0x54, 0x53, 0x45, 0x54, 0x2d, -0x2e, 0x30, 0x32, 0x2d, 0x33, 0x37, 0x2d, 0x67, 0x63, 0x39, 0x66, 0x38, 0x30, 0x33, 0x35, 0x2d, -0x6d, 0x6f, 0x64, 0x64, 0x65, 0x64, 0x00, 0x00, 0x01, 0x0d, 0x02, 0x9a, 0x00, 0x32, 0x00, 0x64, -0x00, 0x00, 0x00, 0x02, 0x4c, 0x4b, 0x40, 0x40, 0x00, 0x00, 0x8a, 0x07, 0xf6, 0xe8, 0x9b, 0x64, -0x62, 0x19, 0xf7, 0x01, 0x02, 0x62, 0xf7, 0xdc, 0xf1, 0xa6, 0x3c, 0xf0, 0xae, 0x64, 0x9c, 0x03, -0x62, 0x98, 0x6a, 0x18, 0x78, 0x97, 0xed, 0x8c, 0x2e, 0x3f, 0xc4, 0x1d, 0x9f, 0xa7, 0xfb, 0x58, -0x26, 0x48, 0x2e, 0x96, 0x9d, 0x33, 0x75, 0x60, 0x6e, 0x33, 0x95, 0xf7, 0x6e, 0x9f, 0x4f, 0xa2, -0xed, 0xd6, 0xa9, 0x83, 0x1b, 0x94, 0x79, 0xee, 0x4f, 0xdc, 0x20, 0xc5, 0x39, 0x74, 0x0d, 0x31, -0x52, 0xc7, 0x25, 0x36, 0x47, 0x06, 0x22, 0x6e, 0x46, 0x11, 0x1a, 0x0b, 0x59, 0xca, 0xaf, 0x12, -0x60, 0x43, 0xeb, 0x5b, 0xbf, 0x28, 0xc3, 0x4f, 0x3a, 0x5e, 0x33, 0x2a, 0x1f, 0xc7, 0xb2, 0xb7, -0x3c, 0xf1, 0x88, 0x91, 0x0f, 0x00, 0x00, 0x71, 0x00, 0x00, 0x01, 0x00, 0x01, 0x64, 0x62, 0x19, -0xf7, 0x01, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x14, 0x00, 0x00, 0x00, 0xc8, 0x00, 0x00, 0x00, 0x00, 0x3b, 0x02, 0x33, 0x80, 0x00, 0x00, 0x00, -0x8a, 0x16, 0x2b, 0xff, 0x08, 0x64, 0x62, 0x19, 0xf7, 0x01, 0x02, 0x39, 0x36, 0x2a, 0x56, 0x61, -0xad, 0x48, 0x3f, 0x4e, 0x13, 0x15, 0x66, 0x43, 0x58, 0xc5, 0xc2, 0x14, 0x6e, 0xb2, 0x72, 0xfa, -0x73, 0xd7, 0xb5, 0x2d, 0x86, 0x14, 0xc2, 0xe8, 0xf7, 0x53, 0x8f, 0x38, 0xea, 0x35, 0x5c, 0xec, -0xe3, 0xc7, 0xc0, 0x46, 0x1c, 0x9f, 0x1d, 0x93, 0x94, 0x31, 0x1f, 0xf8, 0x49, 0xb1, 0x50, 0x4c, -0x2c, 0x2f, 0xc7, 0xe4, 0x0c, 0xaa, 0xd0, 0xa9, 0x53, 0x14, 0xca, 0x06, 0x22, 0x6e, 0x46, 0x11, -0x1a, 0x0b, 0x59, 0xca, 0xaf, 0x12, 0x60, 0x43, 0xeb, 0x5b, 0xbf, 0x28, 0xc3, 0x4f, 0x3a, 0x5e, -0x33, 0x2a, 0x1f, 0xc7, 0xb2, 0xb7, 0x3c, 0xf1, 0x88, 0x91, 0x0f, 0x00, 0x00, 0x71, 0x00, 0x00, -0x03, 0x00, 0x01, 0x64, 0x62, 0x19, 0xf7, 0x01, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x76, -0x04, 0x67, 0x00, 0x00, 0x00, 0x00, 0x8a, 0xd5, 0x4a, 0x70, 0x0c, 0x64, 0x62, 0x19, 0xf7, 0x01, -0x02, 0x54, 0x16, 0x95, 0x41, 0x4f, 0x0e, 0x0f, 0xdf, 0x49, 0xb5, 0x87, 0xdc, 0x26, 0xb4, 0xef, -0x73, 0x3c, 0xb8, 0x19, 0x96, 0x62, 0x87, 0xfa, 0x4f, 0x02, 0x53, 0xbe, 0x12, 0x53, 0x93, 0x4b, -0x57, 0x3b, 0xe9, 0xb9, 0x26, 0x46, 0xda, 0x77, 0xaa, 0xdd, 0x8d, 0xf6, 0x86, 0x22, 0xf0, 0x3f, -0xd5, 0x56, 0xdd, 0xaa, 0xa2, 0x4e, 0x4a, 0x9a, 0x70, 0x81, 0xf8, 0xf9, 0x72, 0x7b, 0xd7, 0x90, -0x48, 0x06, 0x22, 0x6e, 0x46, 0x11, 0x1a, 0x0b, 0x59, 0xca, 0xaf, 0x12, 0x60, 0x43, 0xeb, 0x5b, -0xbf, 0x28, 0xc3, 0x4f, 0x3a, 0x5e, 0x33, 0x2a, 0x1f, 0xc7, 0xb2, 0xb7, 0x3c, 0xf1, 0x88, 0x91, -0x0f, 0x00, 0x00, 0x71, 0x00, 0x00, 0x03, 0x00, 0x01, 0x64, 0x62, 0x19, 0xf7, 0x01, 0x00, 0x00, -0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, -0xc8, 0x00, 0x00, 0x00, 0x00, 0x76, 0x04, 0x67, 0x00, 0x00, 0x00, 0x00, 0x8a, 0xc5, 0x6d, 0x8a, -0x5a, 0x64, 0x62, 0x19, 0xf6, 0x01, 0x02, 0x1d, 0x80, 0x09, 0x30, 0x1a, 0x4b, 0x26, 0x60, 0x6b, -0x9a, 0x54, 0x8d, 0x7f, 0x9b, 0x35, 0x78, 0x76, 0x7a, 0xc1, 0xe5, 0x22, 0xdc, 0x08, 0x77, 0xac, -0x54, 0xc7, 0xc0, 0x9b, 0x13, 0x85, 0x20, 0x2c, 0xa4, 0xa3, 0x7e, 0xc5, 0xde, 0xfd, 0x60, 0x43, -0xdb, 0x2e, 0xb0, 0x5b, 0xcc, 0x95, 0xc1, 0xf3, 0x02, 0x09, 0x8a, 0xe1, 0x55, 0x2a, 0x8a, 0x9a, -0x18, 0xe5, 0xa9, 0xee, 0xcd, 0x11, 0x27, 0x06, 0x22, 0x6e, 0x46, 0x11, 0x1a, 0x0b, 0x59, 0xca, -0xaf, 0x12, 0x60, 0x43, 0xeb, 0x5b, 0xbf, 0x28, 0xc3, 0x4f, 0x3a, 0x5e, 0x33, 0x2a, 0x1f, 0xc7, -0xb2, 0xb7, 0x3c, 0xf1, 0x88, 0x91, 0x0f, 0x00, 0x00, 0x71, 0x00, 0x00, 0x02, 0x00, 0x00, 0x64, -0x62, 0x19, 0xf6, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x96, 0x00, 0x00, 0x00, 0x00, 0x3b, 0x02, 0x33, 0x80, 0x00, -0x00, 0x00, 0x8a, 0x67, 0xa5, 0x58, 0xd4, 0x64, 0x62, 0x19, 0xf6, 0x01, 0x02, 0x5a, 0xa5, 0x3e, -0xb8, 0x73, 0xf5, 0xdf, 0xfc, 0x72, 0x16, 0x52, 0xa1, 0x07, 0x8a, 0x2b, 0xf1, 0xc3, 0x92, 0xc5, -0x87, 0xa4, 0x45, 0x07, 0x1e, 0xb3, 0x7d, 0x4c, 0x1c, 0x47, 0x41, 0x2c, 0x93, 0x14, 0x46, 0x16, -0xba, 0xe4, 0xf9, 0xc9, 0x52, 0x4c, 0x5e, 0x6c, 0x4f, 0xc9, 0xec, 0xde, 0x83, 0x15, 0xe0, 0x8e, -0x39, 0xbe, 0xa9, 0x8f, 0x9d, 0xfe, 0xcf, 0xc4, 0x12, 0x32, 0xa4, 0x17, 0x2b, 0x06, 0x22, 0x6e, -0x46, 0x11, 0x1a, 0x0b, 0x59, 0xca, 0xaf, 0x12, 0x60, 0x43, 0xeb, 0x5b, 0xbf, 0x28, 0xc3, 0x4f, -0x3a, 0x5e, 0x33, 0x2a, 0x1f, 0xc7, 0xb2, 0xb7, 0x3c, 0xf1, 0x88, 0x91, 0x0f, 0x00, 0x00, 0x71, -0x00, 0x00, 0x02, 0x00, 0x00, 0x64, 0x62, 0x19, 0xf6, 0x01, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, -0x00, 0x3b, 0x02, 0x33, 0x80, 0x00, 0x00, 0x00, 0x8a, 0x2f, 0x71, 0xed, 0xec, 0x64, 0x62, 0x19, -0xf6, 0x01, 0x02, 0x75, 0x4f, 0x11, 0x1c, 0x56, 0x9f, 0x4a, 0x9d, 0x6f, 0x98, 0x96, 0x1c, 0x5a, -0x9f, 0x0f, 0xb9, 0x24, 0x23, 0x82, 0x7d, 0x86, 0xcf, 0xbc, 0x41, 0x14, 0x38, 0x76, 0x2e, 0x86, -0x47, 0x96, 0xef, 0x14, 0x91, 0x2e, 0x30, 0xe2, 0x4b, 0x1c, 0x47, 0x2d, 0x4a, 0xdc, 0xf6, 0x79, -0xb6, 0x11, 0x80, 0xcc, 0x51, 0xbb, 0xc4, 0x29, 0x33, 0x60, 0xc1, 0x78, 0x1e, 0x82, 0xe3, 0x40, -0xc0, 0xf7, 0x25, 0x06, 0x22, 0x6e, 0x46, 0x11, 0x1a, 0x0b, 0x59, 0xca, 0xaf, 0x12, 0x60, 0x43, -0xeb, 0x5b, 0xbf, 0x28, 0xc3, 0x4f, 0x3a, 0x5e, 0x33, 0x2a, 0x1f, 0xc7, 0xb2, 0xb7, 0x3c, 0xf1, -0x88, 0x91, 0x0f, 0x00, 0x00, 0x71, 0x00, 0x00, 0x04, 0x00, 0x01, 0x64, 0x62, 0x19, 0xf6, 0x01, -0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, -0x00, 0x00, 0xc8, 0x00, 0x00, 0x00, 0x00, 0xb1, 0x06, 0x9a, 0x80, 0x00, 0x00, 0x00, 0x8a, 0xc8, -0x56, 0xb7, 0xb1, 0x64, 0x62, 0x19, 0xf6, 0x01, 0x02, 0x3c, 0x76, 0x7a, 0x28, 0x5e, 0x65, 0x30, -0xac, 0x0d, 0x0f, 0x43, 0x31, 0x02, 0x56, 0xcd, 0x14, 0x51, 0x46, 0x69, 0x33, 0xa0, 0x12, 0x61, -0x9c, 0x34, 0xc5, 0xd8, 0x9a, 0x0c, 0x81, 0x94, 0xad, 0x5e, 0x98, 0xc4, 0xd0, 0x45, 0x3d, 0x32, -0x84, 0xdd, 0xd7, 0x18, 0x2b, 0xdb, 0x13, 0xa8, 0xfc, 0xb2, 0x0d, 0xd6, 0xf6, 0x8a, 0x97, 0xc7, -0xe9, 0x7e, 0x27, 0xb7, 0x86, 0x7a, 0x3e, 0xee, 0xfa, 0x06, 0x22, 0x6e, 0x46, 0x11, 0x1a, 0x0b, -0x59, 0xca, 0xaf, 0x12, 0x60, 0x43, 0xeb, 0x5b, 0xbf, 0x28, 0xc3, 0x4f, 0x3a, 0x5e, 0x33, 0x2a, -0x1f, 0xc7, 0xb2, 0xb7, 0x3c, 0xf1, 0x88, 0x91, 0x0f, 0x00, 0x00, 0x71, 0x00, 0x00, 0x04, 0x00, -0x01, 0x64, 0x62, 0x19, 0xf6, 0x01, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x01, 0x2c, 0x00, 0x00, 0x00, 0x00, 0xb1, 0x06, 0x9a, -0x80 - }; - -static void test_edge_probability(void) -{ - const double eps = 1e-8; - - struct amount_msat min = AMOUNT_MSAT(10); // known min - struct amount_msat max = AMOUNT_MSAT(19); // known max - - struct amount_msat X = AMOUNT_MSAT(0); // in flight - struct amount_msat f; - - for(int i=0;i<=min.millisatoshis;++i) - { - f.millisatoshis = i; - // prob = 1 - assert(fabs(edge_probability(min,max,X,f)-1.0)< eps); - } - for(int i=max.millisatoshis+1;i<=100;++i) - { - f.millisatoshis = i; - // prob = 0 - assert(fabs(edge_probability(min,max,X,f))< eps); - } - f.millisatoshis=11; - assert(fabs(edge_probability(min,max,X,f)-0.9)< eps); - - f.millisatoshis=12; - assert(fabs(edge_probability(min,max,X,f)-0.8)< eps); - - f.millisatoshis=13; - assert(fabs(edge_probability(min,max,X,f)-0.7)< eps); - - f.millisatoshis=14; - assert(fabs(edge_probability(min,max,X,f)-0.6)< eps); - - f.millisatoshis=15; - assert(fabs(edge_probability(min,max,X,f)-0.5)< eps); - - f.millisatoshis=16; - assert(fabs(edge_probability(min,max,X,f)-0.4)< eps); - - f.millisatoshis=17; - assert(fabs(edge_probability(min,max,X,f)-0.3)< eps); - - f.millisatoshis=18; - assert(fabs(edge_probability(min,max,X,f)-0.2)< eps); - - f.millisatoshis=19; - assert(fabs(edge_probability(min,max,X,f)-0.1)< eps); - - X = AMOUNT_MSAT(5); - - // X=B-X - for(int i=15;i<100;++i) - { - f.millisatoshis = i; - // prob = 0 - assert(fabs(edge_probability(min,max,X,f))< eps); - } - // X=B-X - for(int i=5;i<100;++i) - { - f.millisatoshis = i; - assert(fabs(edge_probability(min,max,X,f))< eps); - } - - // X>=A, 0<=f<=B-X - f.millisatoshis=0; - assert(fabs(edge_probability(min,max,X,f)-1.0)< eps); - f.millisatoshis=1; - assert(fabs(edge_probability(min,max,X,f)-0.8)< eps); - f.millisatoshis=2; - assert(fabs(edge_probability(min,max,X,f)-0.6)< eps); - f.millisatoshis=3; - assert(fabs(edge_probability(min,max,X,f)-0.4)< eps); - f.millisatoshis=4; - assert(fabs(edge_probability(min,max,X,f)-0.2)< eps); - f.millisatoshis=5; - assert(fabs(edge_probability(min,max,X,f)-0.0)< eps); -} - -static void remove_file(char *fname) { assert(!remove(fname)); } - -static void test_flow_to_route(void) -{ - const double eps = 1e-8; - - const tal_t *this_ctx = tal(tmpctx, tal_t); - - char *gossfile; - int fd = tmpdir_mkstemp(this_ctx,"run-testflow.XXXXXX",&gossfile); - tal_add_destructor(gossfile, remove_file); - - assert(write_all(fd,canned_map,sizeof(canned_map))); - struct gossmap *gossmap = gossmap_load(this_ctx,gossfile,NULL); - assert(gossmap); - - // for(struct gossmap_node *node = gossmap_first_node(gossmap); - // node; - // node=gossmap_next_node(gossmap,node)) - // { - // struct node_id node_id; - // gossmap_node_get_id(gossmap,node,&node_id); - // const char *node_hex = node_id_to_hexstr(this_ctx,&node_id); - // printf("node: %s\n",node_hex); - // for(size_t i=0;inum_chans;++i) - // { - // int half; - // const struct gossmap_chan *c = gossmap_nth_chan(gossmap,node,i,&half); - // if(!gossmap_chan_set(c,half)) - // continue; - // - // const struct short_channel_id scid = gossmap_chan_scid(gossmap,c); - // - // const char *scid_str = short_channel_id_to_str(this_ctx,&scid); - // - // printf(" scid: %s, half: %d\n",scid_str,half); - // } - // } - - struct node_id l1, l2, l3, l4, l5; - assert(node_id_from_hexstr("024f9da0d726adf0d9a4a3ac32e328b93ad527ccb9db7077c57a12c6f9fa9add74", 66, &l1)); - assert(node_id_from_hexstr("037f97af8e4fa67c9b8d6970849536f888c304316d015af5129e268e0164961b5e", 66, &l2)); - assert(node_id_from_hexstr("02451e9baff81faf5f410b0bbe9e36ee83bf8f79698e6605a51a9748bc73c7dc28", 66, &l3)); - assert(node_id_from_hexstr("02d1ab24fe53cfcac4a477745235c7ac3e75161a5bf8426ea8e0182ad58cab367f", 66, &l4)); - assert(node_id_from_hexstr("03caec54085508da06f14ffb836166ca22042d0fda69690356232a24b8366c8f85", 66, &l5)); - - struct short_channel_id scid12, scid23, scid34,scid45; - assert(short_channel_id_from_str("113x1x1", 7, &scid12)); - assert(short_channel_id_from_str("113x3x1", 7, &scid23)); - assert(short_channel_id_from_str("113x2x0", 7, &scid34)); - assert(short_channel_id_from_str("113x4x1", 7, &scid45)); - - struct chan_extra_map *chan_extra_map = tal(this_ctx, struct chan_extra_map); - chan_extra_map_init(chan_extra_map); - - struct chan_extra_half *h0,*h1; - struct gossmap_chan *c; - struct amount_sat cap; - struct amount_msat sum_min1_max0,sum_min0_max1; - - // check the bounds channel 1--2 - c = gossmap_find_chan(gossmap,&scid12); - assert(gossmap_chan_get_capacity(gossmap,c,&cap)); - - h0 = get_chan_extra_half_by_chan_verify(gossmap,chan_extra_map,c,0); - assert(h0); - h0->known_min = AMOUNT_MSAT(0); - h0->known_max = AMOUNT_MSAT(500000000); - - h1 = get_chan_extra_half_by_chan_verify(gossmap,chan_extra_map,c,1); - assert(h1); - h1->known_min = AMOUNT_MSAT(500000000); - h1->known_max = AMOUNT_MSAT(1000000000); - - assert(amount_msat_less_eq(h0->known_min,h0->known_max)); - assert(amount_msat_less_eq_sat(h0->known_max,cap)); - assert(amount_msat_less_eq(h1->known_min,h1->known_max)); - assert(amount_msat_less_eq_sat(h1->known_max,cap)); - assert(amount_msat_add(&sum_min1_max0,h1->known_min,h0->known_max)); - assert(amount_msat_add(&sum_min0_max1,h0->known_min,h1->known_max)); - assert(amount_msat_eq_sat(sum_min1_max0,cap)); - assert(amount_msat_eq_sat(sum_min0_max1,cap)); - - // check the bounds channel 2--3 - c = gossmap_find_chan(gossmap,&scid23); - assert(gossmap_chan_get_capacity(gossmap,c,&cap)); - - h1 = get_chan_extra_half_by_chan_verify(gossmap,chan_extra_map,c,1); - assert(h1); - h1->known_min = AMOUNT_MSAT(0); - h1->known_max = AMOUNT_MSAT(2000000000); - - h0 = get_chan_extra_half_by_chan_verify(gossmap,chan_extra_map,c,0); - assert(h0); - h0->known_min = AMOUNT_MSAT(0); - h0->known_max = AMOUNT_MSAT(2000000000); - - assert(amount_msat_less_eq(h0->known_min,h0->known_max)); - assert(amount_msat_less_eq_sat(h0->known_max,cap)); - assert(amount_msat_less_eq(h1->known_min,h1->known_max)); - assert(amount_msat_less_eq_sat(h1->known_max,cap)); - assert(amount_msat_add(&sum_min1_max0,h1->known_min,h0->known_max)); - assert(amount_msat_add(&sum_min0_max1,h0->known_min,h1->known_max)); - assert(amount_msat_eq_sat(sum_min1_max0,cap)); - assert(amount_msat_eq_sat(sum_min0_max1,cap)); - - // check the bounds channel 3--4 - c = gossmap_find_chan(gossmap,&scid34); - assert(gossmap_chan_get_capacity(gossmap,c,&cap)); - - h0 = get_chan_extra_half_by_chan_verify(gossmap,chan_extra_map,c,0); - assert(h0); - h0->known_min = AMOUNT_MSAT(500000000); - h0->known_max = AMOUNT_MSAT(1000000000); - - h1 = get_chan_extra_half_by_chan_verify(gossmap,chan_extra_map,c,1); - assert(h1); - h1->known_min = AMOUNT_MSAT(0); - h1->known_max = AMOUNT_MSAT(500000000); - - assert(amount_msat_less_eq(h0->known_min,h0->known_max)); - assert(amount_msat_less_eq_sat(h0->known_max,cap)); - assert(amount_msat_less_eq(h1->known_min,h1->known_max)); - assert(amount_msat_less_eq_sat(h1->known_max,cap)); - assert(amount_msat_add(&sum_min1_max0,h1->known_min,h0->known_max)); - assert(amount_msat_add(&sum_min0_max1,h0->known_min,h1->known_max)); - assert(amount_msat_eq_sat(sum_min1_max0,cap)); - assert(amount_msat_eq_sat(sum_min0_max1,cap)); - - // check the bounds channel 4--5 - c = gossmap_find_chan(gossmap,&scid45); - assert(gossmap_chan_get_capacity(gossmap,c,&cap)); - - h0 = get_chan_extra_half_by_chan_verify(gossmap,chan_extra_map,c,0); - assert(h0); - h0->known_min = AMOUNT_MSAT(1000000000); - h0->known_max = AMOUNT_MSAT(2000000000); - - h1 = get_chan_extra_half_by_chan_verify(gossmap,chan_extra_map,c,1); - assert(h1); - h1->known_min = AMOUNT_MSAT(1000000000); - h1->known_max = AMOUNT_MSAT(2000000000); - - assert(amount_msat_less_eq(h0->known_min,h0->known_max)); - assert(amount_msat_less_eq_sat(h0->known_max,cap)); - assert(amount_msat_less_eq(h1->known_min,h1->known_max)); - assert(amount_msat_less_eq_sat(h1->known_max,cap)); - assert(amount_msat_add(&sum_min1_max0,h1->known_min,h0->known_max)); - assert(amount_msat_add(&sum_min0_max1,h0->known_min,h1->known_max)); - assert(amount_msat_eq_sat(sum_min1_max0,cap)); - assert(amount_msat_eq_sat(sum_min0_max1,cap)); - - struct flow *F; - struct route *route; - struct sha256 payment_hash; - struct amount_msat deliver; - - if (!hex_decode("0001020304050607080900010203040506070809000102030405060708090102", - strlen("0001020304050607080900010203040506070809000102030405060708090102"), - &payment_hash, sizeof(payment_hash))) - abort(); - - // flow 1->2 - F = tal(this_ctx, struct flow); - F->path = tal_arr(F,const struct gossmap_chan *,1); - F->dirs = tal_arr(F,int,1); - F->path[0]=gossmap_find_chan(gossmap,&scid12); - F->dirs[0]=0; - deliver = AMOUNT_MSAT(250000000); - F->amount = deliver; - route = flow_to_route(this_ctx, 1, 1, payment_hash, 0, gossmap, F); - assert(route); - - assert(amount_msat_eq(route->hops[0].amount, deliver)); - assert(fabs(flow_probability(F, gossmap, chan_extra_map, true) - 0.5)4->5 - F = tal(this_ctx, struct flow); - F->path = tal_arr(F,const struct gossmap_chan *,2); - F->dirs = tal_arr(F,int,2); - F->path[0]=gossmap_find_chan(gossmap,&scid34); - F->path[1]=gossmap_find_chan(gossmap,&scid45); - F->dirs[0]=0; - F->dirs[1]=0; - deliver = AMOUNT_MSAT(250000000); - F->amount=deliver; - route = flow_to_route(this_ctx, 1, 1, payment_hash, 0, gossmap, F); - assert(route); - - assert(amount_msat_eq(route->hops[0].amount, amount_msat(250050016))); - assert(fabs(flow_probability(F, gossmap, chan_extra_map, true) - 1.)3->4->5 - F = tal(this_ctx, struct flow); - F->path = tal_arr(F,const struct gossmap_chan *,3); - F->dirs = tal_arr(F,int,3); - F->path[0]=gossmap_find_chan(gossmap,&scid23); - F->path[1]=gossmap_find_chan(gossmap,&scid34); - F->path[2]=gossmap_find_chan(gossmap,&scid45); - F->dirs[0]=1; - F->dirs[1]=0; - F->dirs[2]=0; - deliver = AMOUNT_MSAT(250000000); - F->amount=deliver; - route = flow_to_route(this_ctx, 1, 1, payment_hash, 0, gossmap, F); - assert(route); - - assert(amount_msat_eq(route->hops[0].amount, amount_msat(250087534))); - assert(fabs(flow_probability(F, gossmap, chan_extra_map, true) - 1. + 250.087534/2000)2->3->4->5 - F = tal(this_ctx, struct flow); - F->path = tal_arr(F,const struct gossmap_chan *,4); - F->dirs = tal_arr(F,int,4); - F->path[0]=gossmap_find_chan(gossmap,&scid12); - F->path[1]=gossmap_find_chan(gossmap,&scid23); - F->path[2]=gossmap_find_chan(gossmap,&scid34); - F->path[3]=gossmap_find_chan(gossmap,&scid45); - F->dirs[0]=0; - F->dirs[1]=1; - F->dirs[2]=0; - F->dirs[3]=0; - deliver = AMOUNT_MSAT(250000000); - F->amount=deliver; - route = flow_to_route(this_ctx, 1, 1, payment_hash, 0, gossmap, F); - assert(route); - - assert(amount_msat_eq(route->hops[0].amount, amount_msat(250112544))); - assert(fabs(flow_probability(F, gossmap, chan_extra_map, true) - 0.43728117)half[0].base_fee = basefee; - c->half[0].proportional_fee = ppm; - struct amount_msat out; - - assert(channel_maximum_forward( - &out, c, 0, in) == RENEPAY_NOERROR); - - // do we satisfy the fee constraint? - assert(check_fee_inequality(in, out, basefee, - ppm)); - - // is this the best we can do? - assert( - amount_msat_add(&out, out, amount_msat(1))); - assert(!check_fee_inequality(in, out, basefee, - ppm)); - } - } - } - tal_free(this_ctx); -} - -int main(int argc, char *argv[]) -{ - common_setup(argv[0]); - - test_edge_probability(); - test_flow_to_route(); - test_channel_maximum_forward(); - - common_shutdown(); -} - diff --git a/plugins/renepay/uncertainty.c b/plugins/renepay/uncertainty.c deleted file mode 100644 index 00c50ebc1134..000000000000 --- a/plugins/renepay/uncertainty.c +++ /dev/null @@ -1,200 +0,0 @@ -#include "config.h" -#include -#include - -void uncertainty_route_success(struct uncertainty *uncertainty, - const struct route *route) -{ - if (!route->hops) - return; - - for (size_t i = 0; i < tal_count(route->hops); i++) { - const struct route_hop *hop = &route->hops[i]; - struct short_channel_id_dir scidd = {hop->scid, hop->direction}; - - // FIXME: check errors here, report back - chan_extra_sent_success(uncertainty->chan_extra_map, &scidd, - route->hops[i].amount); - } -} -void uncertainty_remove_htlcs(struct uncertainty *uncertainty, - const struct route *route) -{ - // FIXME: how could we get the route details of a sendpay that we did - // not send? - if (!route->hops) - return; - - const size_t pathlen = tal_count(route->hops); - for (size_t i = 0; i < pathlen; i++) { - const struct route_hop *hop = &route->hops[i]; - struct short_channel_id_dir scidd = {hop->scid, hop->direction}; - - // FIXME: check error - chan_extra_remove_htlc(uncertainty->chan_extra_map, &scidd, - hop->amount); - } -} - -void uncertainty_commit_htlcs(struct uncertainty *uncertainty, - const struct route *route) -{ - // FIXME: how could we get the route details of a sendpay that we did - // not send? - if (!route->hops) - return; - - const size_t pathlen = tal_count(route->hops); - for (size_t i = 0; i < pathlen; i++) { - const struct route_hop *hop = &route->hops[i]; - struct short_channel_id_dir scidd = {hop->scid, hop->direction}; - - // FIXME: check error - chan_extra_commit_htlc(uncertainty->chan_extra_map, &scidd, - hop->amount); - } -} - -void uncertainty_channel_can_send(struct uncertainty *uncertainty, - struct short_channel_id scid, int direction) -{ - struct short_channel_id_dir scidd = {scid, direction}; - // FIXME: check error - chan_extra_can_send(uncertainty->chan_extra_map, &scidd); -} -void uncertainty_channel_cannot_send(struct uncertainty *uncertainty, - struct short_channel_id scid, - int direction) -{ - struct short_channel_id_dir scidd = {scid, direction}; - // FIXME: check error - chan_extra_cannot_send(uncertainty->chan_extra_map, &scidd); -} - -int uncertainty_update(struct uncertainty *uncertainty, struct gossmap *gossmap) -{ - /* Each channel in chan_extra_map should be either in gossmap or in - * local_gossmods. */ - assert(uncertainty); - struct chan_extra_map *chan_extra_map = uncertainty->chan_extra_map; - assert(chan_extra_map); - struct chan_extra **del_list = tal_arr(NULL, struct chan_extra*, 0); - struct chan_extra_map_iter it; - for (struct chan_extra *ch = chan_extra_map_first(chan_extra_map, &it); - ch; ch = chan_extra_map_next(chan_extra_map, &it)) { - - /* If we cannot find that channel in the gossmap, add it to the - * delete list. */ - if (!gossmap_find_chan(gossmap, &ch->scid)) - tal_arr_expand(&del_list, ch); - } - for(size_t i=0;ichan_extra_map = tal(uncertainty, struct chan_extra_map); - if (uncertainty->chan_extra_map == NULL) - goto function_fail; - - chan_extra_map_init(uncertainty->chan_extra_map); - - return uncertainty; - -function_fail: - return tal_free(uncertainty); -} - -struct chan_extra_map * -uncertainty_get_chan_extra_map(struct uncertainty *uncertainty) -{ - // TODO: do we really need this function? - return uncertainty->chan_extra_map; -} - -/* Add channel to the Uncertainty Network if it doesn't already exist. */ -const struct chan_extra * -uncertainty_add_channel(struct uncertainty *uncertainty, - const struct short_channel_id scid, - struct amount_msat capacity) -{ - const struct chan_extra *ce = - chan_extra_map_get(uncertainty->chan_extra_map, scid); - if (ce) - return ce; - - return new_chan_extra(uncertainty->chan_extra_map, scid, capacity); -} - -bool uncertainty_set_liquidity(struct uncertainty *uncertainty, - const struct short_channel_id_dir *scidd, - struct amount_msat min, - struct amount_msat max) -{ - // FIXME check error - enum renepay_errorcode err = chan_extra_set_liquidity( - uncertainty->chan_extra_map, scidd, min, max); - - return err == RENEPAY_NOERROR; -} - -struct chan_extra *uncertainty_find_channel(struct uncertainty *uncertainty, - const struct short_channel_id scid) -{ - return chan_extra_map_get(uncertainty->chan_extra_map, scid); -} - -enum renepay_errorcode uncertainty_relax(struct uncertainty *uncertainty, - double seconds) -{ - assert(seconds >= 0); - const double fraction = MIN(seconds / TIMER_FORGET_SEC, 1.0); - struct chan_extra_map *chan_extra_map = - uncertainty_get_chan_extra_map(uncertainty); - struct chan_extra_map_iter it; - for (struct chan_extra *ce = chan_extra_map_first(chan_extra_map, &it); - ce; ce = chan_extra_map_next(chan_extra_map, &it)) { - enum renepay_errorcode err = - chan_extra_relax_fraction(ce, fraction); - - if (err) - return err; - } - return RENEPAY_NOERROR; -} diff --git a/plugins/renepay/uncertainty.h b/plugins/renepay/uncertainty.h deleted file mode 100644 index 894a95732b49..000000000000 --- a/plugins/renepay/uncertainty.h +++ /dev/null @@ -1,69 +0,0 @@ -#ifndef LIGHTNING_PLUGINS_RENEPAY_UNCERTAINTY_H -#define LIGHTNING_PLUGINS_RENEPAY_UNCERTAINTY_H -#include "config.h" -#include -#include -#include -#include - -/* FIXME a hard coded constant to indicate a limit on any channel - capacity. Channels for which the capacity is unknown (because they are not - announced) use this value. It makes sense, because if we don't even know the - channel capacity the liquidity could be anything but it will never be greater - than the global number of msats. - It remains to be checked if this value does not lead to overflow somewhere in - the code. */ -#define MAX_CAPACITY (AMOUNT_MSAT(21000000 * MSAT_PER_BTC)) - -struct uncertainty { - struct chan_extra_map *chan_extra_map; -}; - -/* FIXME: add bool return value and WARN_UNUSED_RESULT */ -void uncertainty_route_success(struct uncertainty *uncertainty, - const struct route *route); -void uncertainty_remove_htlcs(struct uncertainty *uncertainty, - const struct route *route); - -void uncertainty_commit_htlcs(struct uncertainty *uncertainty, - const struct route *route); - -void uncertainty_channel_can_send(struct uncertainty *uncertainty, - struct short_channel_id scid, int direction); - -void uncertainty_channel_cannot_send(struct uncertainty *uncertainty, - struct short_channel_id scid, - int direction); - -WARN_UNUSED_RESULT int uncertainty_update(struct uncertainty *uncertainty, - struct gossmap *gossmap); - -struct uncertainty *uncertainty_new(const tal_t *ctx); - -struct chan_extra_map * -uncertainty_get_chan_extra_map(struct uncertainty *uncertainty); - -const struct chan_extra * -uncertainty_add_channel(struct uncertainty *uncertainty, - const struct short_channel_id scid, - struct amount_msat capacity); - -bool uncertainty_set_liquidity(struct uncertainty *uncertainty, - const struct short_channel_id_dir *scidd, - struct amount_msat min, - struct amount_msat max); - -struct chan_extra *uncertainty_find_channel(struct uncertainty *uncertainty, - const struct short_channel_id scid); - -/* Adds randomness to the current state simulating the natural evolution of the - * liquidity in the network. It should be a markovian process with the minimum - * requirement that the transition operator T satisfies: - * T(t1) T(t2) = T(t1+t2) - * i.e. the transition operator is a one parameter semigroup. - * For the moment we omit the continuous and linear aspects of the problem for a - * lack for formulation. */ -enum renepay_errorcode uncertainty_relax(struct uncertainty *uncertainty, - double seconds); - -#endif /* LIGHTNING_PLUGINS_RENEPAY_UNCERTAINTY_H */ diff --git a/tests/test_askrene.py b/tests/test_askrene.py index a87ae3c6ec53..ab6b2044ab2d 100644 --- a/tests/test_askrene.py +++ b/tests/test_askrene.py @@ -16,15 +16,19 @@ def test_layers(node_factory): assert l2.rpc.askrene_listlayers('test_layers') == {'layers': []} expect = {'layer': 'test_layers', - 'disabled_nodes': [], + 'disabled': [], 'created_channels': [], 'constraints': []} l2.rpc.askrene_disable_node('test_layers', l1.info['id']) - expect['disabled_nodes'].append(l1.info['id']) + expect['disabled'].append(l1.info['id']) assert l2.rpc.askrene_listlayers('test_layers') == {'layers': [expect]} assert l2.rpc.askrene_listlayers() == {'layers': [expect]} assert l2.rpc.askrene_listlayers('test_layers2') == {'layers': []} + l2.rpc.askrene_disable_channel('test_layers', "1x2x3", 0) + expect['disabled'].append("1x2x3/0") + assert l2.rpc.askrene_listlayers('test_layers') == {'layers': [expect]} + # Tell it l3 connects to l1! l2.rpc.askrene_create_channel('test_layers', l3.info['id'], @@ -164,6 +168,19 @@ def test_getroutes(node_factory): # Set up l1 with this as the gossip_store l1 = node_factory.get_node(gossip_store_file=gsfile.name) + # Disabling channels makes getroutes fail + l1.rpc.askrene_disable_channel("chans_disabled", "0x1x0") + l1.rpc.askrene_disable_channel("chans_disabled", "0x2x1") + l1.rpc.askrene_disable_channel("chans_disabled", "0x2x3") + # we can also disable by mistake channels that do not exists + l1.rpc.askrene_disable_channel("chans_disabled", "111x222x333") + with pytest.raises(RpcError, match="Could not find route"): + l1.rpc.getroutes(source=nodemap[0], + destination=nodemap[1], + amount_msat=1000, + layers=["chans_disabled"], + maxfee_msat=1000, + final_cltv=99) # Start easy assert l1.rpc.getroutes(source=nodemap[0], destination=nodemap[1],