diff --git a/Makefile b/Makefile index 011fd5f9a05e..f38bb0ccdd38 100644 --- a/Makefile +++ b/Makefile @@ -191,6 +191,7 @@ CCAN_HEADERS := \ $(CCANDIR)/ccan/json_out/json_out.h \ $(CCANDIR)/ccan/likely/likely.h \ $(CCANDIR)/ccan/list/list.h \ + $(CCANDIR)/ccan/lqueue/lqueue.h \ $(CCANDIR)/ccan/mem/mem.h \ $(CCANDIR)/ccan/membuf/membuf.h \ $(CCANDIR)/ccan/noerr/noerr.h \ @@ -448,7 +449,9 @@ else PYTEST_OPTS += -x endif -check-units: +check-units: check-renepay + +check-renepay: check: check-units installcheck check-protos pytest diff --git a/ccan/ccan/lqueue/LICENSE b/ccan/ccan/lqueue/LICENSE new file mode 120000 index 000000000000..2354d12945d3 --- /dev/null +++ b/ccan/ccan/lqueue/LICENSE @@ -0,0 +1 @@ +../../licenses/BSD-MIT \ No newline at end of file diff --git a/ccan/ccan/lqueue/_info b/ccan/ccan/lqueue/_info new file mode 100644 index 000000000000..d6b6232dac39 --- /dev/null +++ b/ccan/ccan/lqueue/_info @@ -0,0 +1,57 @@ +#include "config.h" +#include +#include + +/** + * lqueue - Simple, singly-linked-list queue implementation + * + * This code provides a simple implementation of the Queue abstract + * data type in terms of a singly linked (circular) list. + * + * License: BSD-MIT + * Author: David Gibson + * + * Example: + * #include + * + * struct arg { + * const char *arg; + * struct lqueue_link ql; + * }; + * + * int main(int argc, char *argv[]) + * { + * int i; + * struct arg *a; + * LQUEUE(struct arg, ql) argq = LQUEUE_INIT; + * + * for (i = 0; i < argc; i++) { + * a = malloc(sizeof(*a)); + * a->arg = argv[i]; + * lqueue_enqueue(&argq, a); + * } + * + * printf("Command line arguments in order:\n"); + * + * while (!lqueue_empty(&argq)) { + * a = lqueue_dequeue(&argq); + * printf("Argument: %s\n", a->arg); + * free(a); + * } + * + * return 0; + * } + */ +int main(int argc, char *argv[]) +{ + /* Expect exactly one argument */ + if (argc != 2) + return 1; + + if (strcmp(argv[1], "depends") == 0) { + printf("ccan/tcon\n"); + return 0; + } + + return 1; +} diff --git a/ccan/ccan/lqueue/lqueue.h b/ccan/ccan/lqueue/lqueue.h new file mode 100644 index 000000000000..cff51bb89103 --- /dev/null +++ b/ccan/ccan/lqueue/lqueue.h @@ -0,0 +1,238 @@ +/* Licensed under BSD-MIT - see LICENSE file for details */ +#ifndef CCAN_LQUEUE_H +#define CCAN_LQUEUE_H + +#include +#include +#include + +#include + +/** + * struct lqueue_link - a queue link + * @next: next entry, or front of queue, if this is the back + * + * This is used as a link within a queue entry. + * + * Example: + * struct waiter { + * char *name; + * struct lqueue_link ql; + * }; + */ +struct lqueue_link { + struct lqueue_link *next; +}; + +/** + * struct lqueue_ - a queue (internal type) + * @b: the back of the queue (NULL if empty) + */ +struct lqueue_ { + struct lqueue_link *back; +}; + +/** + * LQUEUE - declare a queue + * @type: the type of elements in the queue + * @link: the field containing the lqueue_link in @type + * + * The LQUEUE macro declares an lqueue. It can be prepended by + * "static" to define a static lqueue. The queue begins in undefined + * state, you must either initialize with LQUEUE_INIT, or call + * lqueue_init() before using it. + * + * See also: + * lqueue_init() + * + * Example: + * struct element { + * int value; + * struct lqueue_link link; + * }; + * LQUEUE(struct element, link) my_queue; + */ +#define LQUEUE(etype, link) \ + TCON_WRAP(struct lqueue_, \ + TCON_CONTAINER(canary, etype, link)) + +/** + * LQUEUE_INIT - initializer for an empty queue + * + * The LQUEUE_INIT macro returns a suitable initializer for a queue + * defined with LQUEUE. + * + * Example: + * struct element { + * int value; + * struct lqueue_link link; + * }; + * LQUEUE(struct element, link) my_queue = LQUEUE_INIT; + * + * assert(lqueue_empty(&my_queue)); + */ +#define LQUEUE_INIT \ + TCON_WRAP_INIT({ NULL, }) + +/** + * lqueue_entry - convert an lqueue_link back into the structure containing it. + * @q: the queue + * @l: the lqueue_link + * + * Example: + * struct waiter { + * char *name; + * struct lqueue_link ql; + * } w; + * LQUEUE(struct waiter, ql) my_queue; + * assert(lqueue_entry(&my_queue, &w.ql) == &w); + */ +#define lqueue_entry(q_, l_) tcon_container_of((q_), canary, (l_)) + +/** + * lqueue_init_from_back - initialize a queue with a specific back element + * @s: the lqueue to initialize + * @e: pointer to the back element of the new queue + * + * USE WITH CAUTION: This is for handling unusual cases where you have + * a pointer to an element in a previously constructed queue but can't + * conveniently pass around a normal struct lqueue. Usually you + * should use lqueue_init(). + * + * Example: + * struct element { + * int value; + * struct lqueue_link link; + * } el; + * LQUEUE(struct element, link) queue1; + * LQUEUE(struct element, link) queue2; + * + * lqueue_enqueue(&queue1, &el); + * + * lqueue_init_from_back(&queue2, lqueue_back(&queue1)); + */ +#define lqueue_init_from_back(q_, e_) \ + (lqueue_init_(tcon_unwrap(q_), tcon_member_of((q_), canary, (e_)))) + +/** + * lqueue_init - initialize a queue + * @h: the lqueue to set to an empty queue + * + * Example: + * struct element { + * int value; + * struct lqueue_link link; + * }; + * LQUEUE(struct element, link) *qp = malloc(sizeof(*qp)); + * lqueue_init(qp); + */ +#define lqueue_init(q_) \ + (lqueue_init_(tcon_unwrap(q_), NULL)) +static inline void lqueue_init_(struct lqueue_ *q, struct lqueue_link *back) +{ + q->back = back; +} + +/** + * lqueue_empty - is a queue empty? + * @q: the queue + * + * If the queue is empty, returns true. + */ +#define lqueue_empty(q_) \ + lqueue_empty_(tcon_unwrap(q_)) +static inline bool lqueue_empty_(const struct lqueue_ *q) +{ + return (q->back == NULL); +} + +/** + * lqueue_front - get front entry in a queue + * @q: the queue + * + * If the queue is empty, returns NULL. + * + * Example: + * struct element *f; + * + * f = lqueue_front(qp); + * assert(lqueue_dequeue(qp) == f); + */ +#define lqueue_front(q_) \ + lqueue_entry((q_), lqueue_front_(tcon_unwrap(q_))) +static inline struct lqueue_link *lqueue_front_(const struct lqueue_ *q) +{ + if (!q->back) + return NULL; + else + return q->back->next; +} + +/** + * lqueue_back - get back entry in a queue + * @q: the queue + * + * If the queue is empty, returns NULL. + * + * Example: + * struct element b; + * + * lqueue_enqueue(qp, &b); + * assert(lqueue_back(qp) == &b); + */ +#define lqueue_back(q_) \ + lqueue_entry((q_), lqueue_back_(tcon_unwrap(q_))) +static inline struct lqueue_link *lqueue_back_(const struct lqueue_ *q) +{ + return q->back; +} + +/** + * lqueue_enqueue - add an entry to the back of a queue + * @q: the queue to add the node to + * @e: the item to enqueue + * + * The lqueue_link does not need to be initialized; it will be overwritten. + */ +#define lqueue_enqueue(q_, e_) \ + lqueue_enqueue_(tcon_unwrap(q_), tcon_member_of((q_), canary, (e_))) +static inline void lqueue_enqueue_(struct lqueue_ *q, struct lqueue_link *e) +{ + if (lqueue_empty_(q)) { + /* New entry will be both front and back of queue */ + e->next = e; + q->back = e; + } else { + e->next = lqueue_front_(q); + q->back->next = e; + q->back = e; + } +} + +/** + * lqueue_dequeue - remove and return the entry from the front of the queue + * @q: the queue + * + * Note that this leaves the returned entry's link in an undefined + * state; it can be added to another queue, but not deleted again. + */ +#define lqueue_dequeue(q_) \ + lqueue_entry((q_), lqueue_dequeue_(tcon_unwrap(q_))) +static inline struct lqueue_link *lqueue_dequeue_(struct lqueue_ *q) +{ + struct lqueue_link *front; + + if (lqueue_empty_(q)) + return NULL; + + front = lqueue_front_(q); + if (front == lqueue_back_(q)) { + assert(front->next == front); + q->back = NULL; + } else { + q->back->next = front->next; + } + return front; +} + +#endif /* CCAN_LQUEUE_H */ diff --git a/ccan/ccan/lqueue/test/run.c b/ccan/ccan/lqueue/test/run.c new file mode 100644 index 000000000000..6791d2518890 --- /dev/null +++ b/ccan/ccan/lqueue/test/run.c @@ -0,0 +1,69 @@ +#include "config.h" + +#include +#include + +struct waiter { + const char *name; + struct lqueue_link ql; +}; + +int main(void) +{ + LQUEUE(struct waiter, ql) q = LQUEUE_INIT; + struct waiter a = { "Alice" }; + struct waiter b = { "Bob" }; + struct waiter c = { "Carol" }; + struct waiter *waiter; + + /* This is how many tests you plan to run */ + plan_tests(25); + + ok1(lqueue_empty(&q)); + ok1(lqueue_front(&q) == NULL); + ok1(lqueue_back(&q) == NULL); + + lqueue_enqueue(&q, &a); + + ok1(!lqueue_empty(&q)); + ok1(lqueue_front(&q) == &a); + ok1(lqueue_back(&q) == &a); + + lqueue_enqueue(&q, &b); + + ok1(!lqueue_empty(&q)); + ok1(lqueue_front(&q) == &a); + ok1(lqueue_back(&q) == &b); + + lqueue_enqueue(&q, &c); + + ok1(!lqueue_empty(&q)); + ok1(lqueue_front(&q) == &a); + ok1(lqueue_back(&q) == &c); + + waiter = lqueue_dequeue(&q); + ok1(waiter == &a); + + ok1(!lqueue_empty(&q)); + ok1(lqueue_front(&q) == &b); + ok1(lqueue_back(&q) == &c); + + waiter = lqueue_dequeue(&q); + ok1(waiter == &b); + + ok1(!lqueue_empty(&q)); + ok1(lqueue_front(&q) == &c); + ok1(lqueue_back(&q) == &c); + + waiter = lqueue_dequeue(&q); + ok1(waiter == &c); + + ok1(lqueue_empty(&q)); + ok1(lqueue_front(&q) == NULL); + ok1(lqueue_back(&q) == NULL); + + ok1(lqueue_dequeue(&q) == NULL); + + /* This exits depending on whether all tests passed */ + return exit_status(); +} diff --git a/plugins/Makefile b/plugins/Makefile index 4ed25c9669f3..754359866a07 100644 --- a/plugins/Makefile +++ b/plugins/Makefile @@ -100,7 +100,8 @@ C_PLUGINS := \ plugins/offers \ plugins/pay \ plugins/txprepare \ - plugins/spenderp + plugins/spenderp \ + plugins/cln-renepay ifeq ($(HAVE_SQLITE3),1) C_PLUGINS += plugins/sql diff --git a/plugins/renepay/Makefile b/plugins/renepay/Makefile index a3e35956c891..b0149fb1c124 100644 --- a/plugins/renepay/Makefile +++ b/plugins/renepay/Makefile @@ -1,5 +1,7 @@ -PLUGIN_RENEPAY_SRC := plugins/renepay/pay.c plugins/renepay/pay_flow.c plugins/renepay/flow.c plugins/renepay/not_mcf.c -PLUGIN_RENEPAY_HDRS := plugins/renepay/pay.h plugins/renepay/pay_flow.h plugins/renepay/flow.h plugins/renepay/mcf.h +PLUGIN_RENEPAY_SRC := plugins/renepay/pay.c plugins/renepay/pay_flow.c plugins/renepay/flow.c plugins/renepay/mcf.c plugins/renepay/dijkstra.c \ + plugins/renepay/debug.c plugins/renepay/payment.c plugins/renepay/uncertainty_network.c +PLUGIN_RENEPAY_HDRS := plugins/renepay/pay.h plugins/renepay/pay_flow.h plugins/renepay/flow.h plugins/renepay/mcf.h plugins/renepay/heap.h plugins/renepay/dijkstra.h \ + plugins/renepay/debug.h plugins/renepay/payment.h plugins/renepay/uncertainty_network.h PLUGIN_RENEPAY_OBJS := $(PLUGIN_RENEPAY_SRC:.c=.o) # Make sure these depend on everything. diff --git a/plugins/renepay/debug.c b/plugins/renepay/debug.c new file mode 100644 index 000000000000..51b19ef808f3 --- /dev/null +++ b/plugins/renepay/debug.c @@ -0,0 +1,51 @@ +#include + +void _debug_exec_branch(const char* fname,const char* fun, int lineno) +{ + FILE *f = fopen(fname,"a"); + fprintf(f,"executing line: %d (%s)\n",lineno,fun); + fclose(f); +} + +void _debug_outreq(const char *fname, const struct out_req *req) +{ + FILE *f = fopen(fname,"a"); + size_t len; + const char * str = json_out_contents(req->js->jout,&len); + fprintf(f,"%s",str); + if (req->errcb) + fprintf(f,"}"); + fprintf(f,"}\n"); + fclose(f); +} + +void _debug_call(const char* fname, const char* fun) +{ + pthread_t tid = pthread_self(); + FILE *f = fopen(fname,"a"); + fprintf(f,"calling function: %s (pthread_t %ld)\n",fun,tid); + fclose(f); +} + +void _debug_reply(const char* fname, const char* buf,const jsmntok_t *toks) +{ + FILE *f = fopen(fname,"a"); + fprintf(f,"%.*s\n\n", + json_tok_full_len(toks), + json_tok_full(buf, toks)); + fclose(f); +} + +void _debug_info(const char* fname, const char *fmt, ...) +{ + FILE *f = fopen(fname,"a"); + + va_list args; + va_start(args, fmt); + + vfprintf(f,fmt,args); + + va_end(args); + fclose(f); +} + diff --git a/plugins/renepay/debug.h b/plugins/renepay/debug.h new file mode 100644 index 000000000000..d8f221539455 --- /dev/null +++ b/plugins/renepay/debug.h @@ -0,0 +1,57 @@ +#ifndef LIGHTNING_PLUGINS_RENEPAY_DEBUG_H +#define LIGHTNING_PLUGINS_RENEPAY_DEBUG_H + +#include +#include +#include +#include +#include +#include +#include +#include + +void _debug_outreq(const char *fname, const struct out_req *req); +void _debug_reply(const char* fname, const char* buf,const jsmntok_t *toks); +void _debug_info(const char* fname, const char *fmt, ...); +void _debug_call(const char* fname, const char* fun); +void _debug_exec_branch(const char* fname,const char* fun, int lineno); + +#ifndef MYLOG +#define MYLOG "/tmp/debug.txt" +#endif + + +/* All debug information goes to a file. */ +#ifdef RENEPAY_UNITTEST + +#define debug_info(...) \ + _debug_info(MYLOG,__VA_ARGS__) + +#define debug_err(...) \ + {_debug_info(MYLOG,__VA_ARGS__); abort();} + +#define debug_paynote(p,...) \ + {payment_note(p,__VA_ARGS__);_debug_info(MYLOG,__VA_ARGS__);} + +#else +/* Debugin information goes either to payment notes or to lightningd log. */ + +#include +#include + +#define debug_info(...) \ + plugin_log(pay_plugin->plugin,LOG_DBG,__VA_ARGS__) + +#define debug_err(...) \ + plugin_err(pay_plugin->plugin,__VA_ARGS__) + +#define debug_paynote(p,...) \ + payment_note(p,__VA_ARGS__); + +#endif + +#define debug_assert(expr) \ + if(!(expr)) debug_err("Assertion failed %s, file: %s, line %d", #expr,__FILE__,__LINE__) + + +#endif diff --git a/plugins/renepay/dijkstra.c b/plugins/renepay/dijkstra.c new file mode 100644 index 000000000000..2f1b9574f63d --- /dev/null +++ b/plugins/renepay/dijkstra.c @@ -0,0 +1,170 @@ +#include + +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 user 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; +} + +static void dijkstra_destroy(struct dijkstra *ptr UNUSED) +{ + global_dijkstra=NULL; +} + +void dijkstra_free(void) +{ + if(global_dijkstra) + { + tal_free(global_dijkstra); + assert(global_dijkstra==NULL); + } +} + +/* Allocation of resources for the heap. */ +void dijkstra_malloc(const tal_t *ctx, const size_t max_num_nodes) +{ + dijkstra_free(); + + global_dijkstra = tal(ctx,struct dijkstra); + tal_add_destructor(global_dijkstra,dijkstra_destroy); + + global_dijkstra->distance = tal_arr(global_dijkstra,s64,max_num_nodes); + global_dijkstra->base = tal_arr(global_dijkstra,u32,max_num_nodes); + global_dijkstra->heapptr = tal_arrz(global_dijkstra,u32*,max_num_nodes); + + global_dijkstra->heapsize=0; + + global_dijkstra->gheap_ctx.fanout=2; + global_dijkstra->gheap_ctx.page_chunks=1024; + global_dijkstra->gheap_ctx.item_size=sizeof(global_dijkstra->base[0]); + global_dijkstra->gheap_ctx.less_comparer=dijkstra_less_comparer; + global_dijkstra->gheap_ctx.less_comparer_ctx=NULL; + global_dijkstra->gheap_ctx.item_mover=dijkstra_item_mover; +} + + +void dijkstra_init(void) +{ + const size_t max_num_nodes = tal_count(global_dijkstra->distance); + global_dijkstra->heapsize=0; + for(size_t i=0;idistance[i]=INFINITE; + global_dijkstra->heapptr[i] = NULL; + } +} +size_t dijkstra_size(void) +{ + return global_dijkstra->heapsize; +} + +size_t dijkstra_maxsize(void) +{ + return tal_count(global_dijkstra->distance); +} + +static void dijkstra_append(u32 node_idx, s64 distance) +{ + assert(dijkstra_size() < dijkstra_maxsize()); + assert(node_idx < dijkstra_maxsize()); + + const size_t pos = global_dijkstra->heapsize; + + global_dijkstra->base[pos]=node_idx; + global_dijkstra->distance[node_idx]=distance; + global_dijkstra->heapptr[node_idx] = &(global_dijkstra->base[pos]); + global_dijkstra->heapsize++; +} +void dijkstra_update(u32 node_idx, s64 distance) +{ + assert(node_idx < dijkstra_maxsize()); + + if(!global_dijkstra->heapptr[node_idx]) + { + // not in the heap + dijkstra_append(node_idx,distance); + gheap_restore_heap_after_item_increase( + &global_dijkstra->gheap_ctx, + global_dijkstra->base, + global_dijkstra->heapsize, + global_dijkstra->heapptr[node_idx] + - global_dijkstra->base); + return; + } + + if(global_dijkstra->distance[node_idx] > distance) + { + // distance decrease + global_dijkstra->distance[node_idx] = distance; + + gheap_restore_heap_after_item_increase( + &global_dijkstra->gheap_ctx, + global_dijkstra->base, + global_dijkstra->heapsize, + global_dijkstra->heapptr[node_idx] + - global_dijkstra->base); + }else + { + // distance increase + global_dijkstra->distance[node_idx] = distance; + + gheap_restore_heap_after_item_decrease( + &global_dijkstra->gheap_ctx, + global_dijkstra->base, + global_dijkstra->heapsize, + global_dijkstra->heapptr[node_idx] + - global_dijkstra->base); + + } + // assert(gheap_is_heap(&global_dijkstra->gheap_ctx, + // global_dijkstra->base, + // dijkstra_size())); +} +u32 dijkstra_top(void) +{ + return global_dijkstra->base[0]; +} +bool dijkstra_empty(void) +{ + return global_dijkstra->heapsize==0; +} +void dijkstra_pop(void) +{ + if(global_dijkstra->heapsize==0) + return; + + const u32 top = dijkstra_top(); + assert(global_dijkstra->heapptr[top]==global_dijkstra->base); + + gheap_pop_heap( + &global_dijkstra->gheap_ctx, + global_dijkstra->base, + global_dijkstra->heapsize--); + + global_dijkstra->heapptr[top]=NULL; +} +const s64* dijkstra_distance_data(void) +{ + return global_dijkstra->distance; +} diff --git a/plugins/renepay/dijkstra.h b/plugins/renepay/dijkstra.h new file mode 100644 index 000000000000..1f351f1d3be8 --- /dev/null +++ b/plugins/renepay/dijkstra.h @@ -0,0 +1,49 @@ +#ifndef LIGHTNING_PLUGINS_RENEPAY_DIJKSTRA_H +#define LIGHTNING_PLUGINS_RENEPAY_DIJKSTRA_H + +#include +#include +#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; +}; + +/* Allocation of resources for the heap. */ +void dijkstra_malloc(const tal_t *ctx, const size_t max_num_nodes); + +/* Free resources */ +void dijkstra_free(void); + +/* Initialization of the heap for a new Dijkstra search. */ +void dijkstra_init(void); + +/* Inserts a new element in the heap. If node_idx was already in the heap then + * its distance value is updated. */ +void dijkstra_update(u32 node_idx, s64 distance); + +u32 dijkstra_top(void); +bool dijkstra_empty(void); +void dijkstra_pop(void); + +const s64* dijkstra_distance_data(void); + +/* Number of elements on the heap. */ +size_t dijkstra_size(void); + +/* Maximum number of elements the heap can host */ +size_t dijkstra_maxsize(void); + +#endif // LIGHTNING_PLUGINS_RENEPAY_DIJKSTRA_H diff --git a/plugins/renepay/flow.c b/plugins/renepay/flow.c index 5db859db3679..6cf4f19b6cfe 100644 --- a/plugins/renepay/flow.c +++ b/plugins/renepay/flow.c @@ -1,9 +1,14 @@ #include "config.h" #include #include +#include +#include #include #include +#include #include +#include +#include #ifndef SUPERVERBOSE #define SUPERVERBOSE(...) @@ -11,60 +16,292 @@ #define SUPERVERBOSE_ENABLED 1 #endif -/* Helper to access the half chan at flow index idx */ -const struct half_chan *flow_edge(const struct flow *flow, size_t idx) +bool chan_extra_is_busy(struct chan_extra const * const ce) { - assert(idx < tal_count(flow->path)); - return &flow->path[idx]->half[flow->dirs[idx]]; + if(ce==NULL)return false; + return ce->half[0].num_htlcs || ce->half[1].num_htlcs; } -/* Assuming a uniform distribution, what is the chance this f gets through? - * - * This is the "(c_e + 1 − f_e) / (c_e + 1)" in the paper. - */ -static double edge_probability(struct amount_msat min, struct amount_msat max, - struct amount_msat f) +const char *fmt_chan_extra_map( + const tal_t *ctx, + struct chan_extra_map* chan_extra_map) { - struct amount_msat range_plus_one, numerator; - - /* If f is <= known minimum, probability is 1. */ - if (!amount_msat_sub(&f, f, min)) - return 1.0; + 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 = + type_to_string(this_ctx,struct short_channel_id,&ch->scid); + for(int dir=0;dir<2;++dir) + { + tal_append_fmt(&buff,"%s[%d]:(%s,%s)\n",scid_str,dir, + type_to_string(this_ctx,struct amount_msat,&ch->half[dir].known_min), + type_to_string(this_ctx,struct amount_msat,&ch->half[dir].known_max)); + } + } + tal_free(this_ctx); + return buff; +} - /* +1 because the number of elements in the range is min-max + 1 */ - if (!amount_msat_sub(&range_plus_one, max, min)) - abort(); - if (!amount_msat_add(&range_plus_one, range_plus_one, AMOUNT_MSAT(1))) - abort(); +struct chan_extra *new_chan_extra( + struct chan_extra_map *chan_extra_map, + const struct short_channel_id scid, + struct amount_msat capacity) +{ + struct chan_extra *ce = tal(chan_extra_map, struct chan_extra); - /* If f > capacity, probability is 0 */ - if (!amount_msat_sub(&numerator, range_plus_one, f)) - return 0.0; + 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; + } + chan_extra_map_add(chan_extra_map, 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; +} - return amount_msat_ratio(numerator, range_plus_one); +/* This helper function preserves the uncertainty network invariant after the + * knowledge is updated. It assumes that the (channel,!dir) knowledge is + * correct. */ +void chan_extra_adjust_half(struct chan_extra *ce, + int dir) +{ + if(!amount_msat_sub(&ce->half[dir].known_max,ce->capacity,ce->half[!dir].known_min)) + { + debug_err("%s cannot substract capacity=%s and known_min=%s", + __PRETTY_FUNCTION__, + type_to_string(tmpctx,struct amount_msat,&ce->capacity), + type_to_string(tmpctx,struct amount_msat,&ce->half[!dir].known_min) + ); + } + if(!amount_msat_sub(&ce->half[dir].known_min,ce->capacity,ce->half[!dir].known_max)) + { + debug_err("%s cannot substract capacity=%s and known_max=%s", + __PRETTY_FUNCTION__, + type_to_string(tmpctx,struct amount_msat,&ce->capacity), + type_to_string(tmpctx,struct amount_msat,&ce->half[!dir].known_max) + ); + } } -bool flow_path_eq(const struct gossmap_chan **path1, - const int *dirs1, - const struct gossmap_chan **path2, - const int *dirs2) + +/* Update the knowledge that this (channel,direction) can send x msat.*/ +static void chan_extra_can_send_( + struct chan_extra *ce, + int dir, + struct amount_msat x) { - if (tal_count(path1) != tal_count(path2)) - return false; - for (size_t i = 0; i < tal_count(path1); i++) { - if (path1[i] != path2[i]) - return false; - if (dirs1[i] != dirs2[i]) - return false; + if(amount_msat_greater(x,ce->capacity)) + { + debug_err("%s unexpected capacity=%s is less than x=%s", + __PRETTY_FUNCTION__, + type_to_string(tmpctx,struct amount_msat,&ce->capacity), + type_to_string(tmpctx,struct amount_msat,&x) + ); + x = ce->capacity; + } + + 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); + + chan_extra_adjust_half(ce,!dir); +} +void chan_extra_can_send( + struct chan_extra_map *chan_extra_map, + struct short_channel_id scid, + int dir, + struct amount_msat x) +{ + struct chan_extra *ce = chan_extra_map_get(chan_extra_map, + scid); + if(!ce) + { + debug_err("%s unexpected chan_extra ce is NULL", + __PRETTY_FUNCTION__); + } + if(!amount_msat_add(&x,x,ce->half[dir].htlc_total)) + { + debug_err("%s (line %d) cannot add x=%s and htlc_total=%s", + __PRETTY_FUNCTION__,__LINE__, + type_to_string(tmpctx,struct amount_msat,&x), + type_to_string(tmpctx,struct amount_msat,&ce->half[dir].htlc_total)); } - return true; + chan_extra_can_send_(ce,dir,x); } -static void destroy_chan_extra(struct chan_extra *ce, - struct chan_extra_map *chan_extra_map) +/* Update the knowledge that this (channel,direction) cannot send x msat.*/ +void chan_extra_cannot_send( + struct payment *p, + struct chan_extra_map *chan_extra_map, + struct short_channel_id scid, + int dir, + struct amount_msat x) { - chan_extra_map_del(chan_extra_map, ce); + struct chan_extra *ce = chan_extra_map_get(chan_extra_map, + scid); + if(!ce) + { + debug_err("%s (line %d) unexpected chan_extra ce is NULL", + __PRETTY_FUNCTION__,__LINE__); + } + + /* If a channel cannot send x it means that the upper bound for the + * liquidity is MAX_L < x + htlc_total */ + if(!amount_msat_add(&x,x,ce->half[dir].htlc_total)) + { + debug_err("%s (line %d) cannot add x=%s and htlc_total=%s", + __PRETTY_FUNCTION__,__LINE__, + type_to_string(tmpctx,struct amount_msat,&x), + type_to_string(tmpctx,struct amount_msat,&ce->half[dir].htlc_total)); + } + + if(!amount_msat_sub(&x,x,AMOUNT_MSAT(1))) + { + debug_err("%s (line %d) unexpected x=%s is less than 0msat", + __PRETTY_FUNCTION__,__LINE__, + type_to_string(tmpctx,struct amount_msat,&x) + ); + x = AMOUNT_MSAT(0); + } + + ce->half[dir].known_min = amount_msat_min(ce->half[dir].known_min,x); + ce->half[dir].known_max = amount_msat_min(ce->half[dir].known_max,x); + + debug_paynote(p,"Update chan knowledge scid=%s, dir=%d: [%s,%s]", + type_to_string(tmpctx,struct short_channel_id,&scid), + dir, + type_to_string(tmpctx,struct amount_msat,&ce->half[dir].known_min), + type_to_string(tmpctx,struct amount_msat,&ce->half[dir].known_max)); + + chan_extra_adjust_half(ce,!dir); +} +/* Update the knowledge that this (channel,direction) has liquidity x.*/ +static void chan_extra_set_liquidity_( + struct chan_extra *ce, + int dir, + struct amount_msat x) +{ + if(amount_msat_greater(x,ce->capacity)) + { + debug_err("%s unexpected capacity=%s is less than x=%s", + __PRETTY_FUNCTION__, + type_to_string(tmpctx,struct amount_msat,&ce->capacity), + type_to_string(tmpctx,struct amount_msat,&x) + ); + x = ce->capacity; + } + + ce->half[dir].known_min = x; + ce->half[dir].known_max = x; + + chan_extra_adjust_half(ce,!dir); +} +void chan_extra_set_liquidity( + struct chan_extra_map *chan_extra_map, + struct short_channel_id scid, + int dir, + struct amount_msat x) +{ + struct chan_extra *ce = chan_extra_map_get(chan_extra_map, + scid); + if(!ce) + { + debug_err("%s unexpected chan_extra ce is NULL", + __PRETTY_FUNCTION__); + } + chan_extra_set_liquidity_(ce,dir,x); +} +/* Update the knowledge that this (channel,direction) has sent x msat.*/ +static void chan_extra_sent_success_( + struct chan_extra *ce, + int dir, + struct amount_msat x) +{ + if(amount_msat_greater(x,ce->capacity)) + { + debug_err("%s unexpected capacity=%s is less than x=%s", + __PRETTY_FUNCTION__, + type_to_string(tmpctx,struct amount_msat,&ce->capacity), + type_to_string(tmpctx,struct amount_msat,&x) + ); + x = ce->capacity; + } + + struct amount_msat new_a, new_b; + + if(!amount_msat_sub(&new_a,ce->half[dir].known_min,x)) + new_a = AMOUNT_MSAT(0); + if(!amount_msat_sub(&new_b,ce->half[dir].known_max,x)) + new_b = AMOUNT_MSAT(0); + + ce->half[dir].known_min = new_a; + ce->half[dir].known_max = new_b; + + chan_extra_adjust_half(ce,!dir); +} +void chan_extra_sent_success( + struct chan_extra_map *chan_extra_map, + struct short_channel_id scid, + int dir, + struct amount_msat x) +{ + struct chan_extra *ce = chan_extra_map_get(chan_extra_map, + scid); + if(!ce) + { + debug_err("%s unexpected chan_extra ce is NULL", + __PRETTY_FUNCTION__); + } + chan_extra_sent_success_(ce,dir,x); +} +/* Forget a bit about this (channel,direction) state. */ +static void chan_extra_relax_( + struct chan_extra *ce, + int dir, + struct amount_msat down, + struct amount_msat up) +{ + struct amount_msat new_a, new_b; + + 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 = amount_msat_min(new_b,ce->capacity); + + ce->half[dir].known_min = new_a; + ce->half[dir].known_max = new_b; + + chan_extra_adjust_half(ce,!dir); } +void chan_extra_relax( + struct chan_extra_map *chan_extra_map, + struct short_channel_id scid, + int dir, + struct amount_msat x, + struct amount_msat y) +{ + struct chan_extra *ce = chan_extra_map_get(chan_extra_map, + scid); + if(!ce) + { + debug_err("%s unexpected chan_extra ce is NULL", + __PRETTY_FUNCTION__); + } + chan_extra_relax_(ce,dir,x,y); +} + /* Returns either NULL, or an entry from the hash */ struct chan_extra_half * @@ -79,7 +316,6 @@ get_chan_extra_half_by_scid(struct chan_extra_map *chan_extra_map, return NULL; return &ce->half[dir]; } - /* Helper if we have a gossmap_chan */ struct chan_extra_half * get_chan_extra_half_by_chan(const struct gossmap *gossmap, @@ -92,26 +328,142 @@ get_chan_extra_half_by_chan(const struct gossmap *gossmap, dir); } -struct chan_extra_half *new_chan_extra_half(struct chan_extra_map *chan_extra_map, - const struct short_channel_id scid, - int dir, - struct amount_msat capacity) + +// 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) { - struct chan_extra *ce = tal(chan_extra_map, struct chan_extra); + + const struct short_channel_id scid = gossmap_chan_scid(gossmap,chan); + struct chan_extra_half *h = get_chan_extra_half_by_scid( + chan_extra_map,scid,dir); + 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)) + { + debug_err("%s (line %d) unable convert sat to msat or " + "get channel capacity", + __PRETTY_FUNCTION__, + __LINE__); + } + h = & new_chan_extra(chan_extra_map,scid,cap_msat)->half[dir]; + + } + return h; +} - ce->scid = scid; - 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; +/* 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). + */ +static 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 tal_t *this_ctx = tal(tmpctx,tal_t); + + // SUPERVERBOSE("%s: with min=%ld, max=%ld, in_flight=%ld, flow=%ld\n", + // __PRETTY_FUNCTION__,min.millisatoshis,max.millisatoshis, + // in_flight.millisatoshis,f.millisatoshis); + + 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_add(&B,B,one)) + { + debug_err("%s (line %d) cannot add B=%s and %s", + __PRETTY_FUNCTION__, + __LINE__, + type_to_string(this_ctx, struct amount_msat, &B), + type_to_string(this_ctx, struct amount_msat, &one)); + } + // in_flight cannot be greater than max + if(!amount_msat_sub(&B,B,in_flight)) + { + debug_err("%s (line %d) in_flight=%s cannot be greater than B=%s", + __PRETTY_FUNCTION__, + __LINE__, + type_to_string(this_ctx, struct amount_msat, &in_flight), + type_to_string(this_ctx, struct amount_msat, &B)); + } + 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)) + { + debug_err("%s (line %d) B=%s must be greater than A=%s", + __PRETTY_FUNCTION__, + __LINE__, + type_to_string(this_ctx, struct amount_msat, &B), + type_to_string(this_ctx, struct amount_msat, &A)); } - /* Remove self from map when done */ - chan_extra_map_add(chan_extra_map, ce); - tal_add_destructor2(ce, destroy_chan_extra, chan_extra_map); - return &ce->half[dir]; + struct amount_msat numerator; // MAX(0,B-f) + + if(!amount_msat_sub(&numerator,B,f)) + numerator = AMOUNT_MSAT(0); + + tal_free(this_ctx); + return amount_msat_less_eq(f,A) ? 1.0 : amount_msat_ratio(numerator,denominator); } + + + + + +// TODO(eduardo): remove this function, is a duplicate void remove_completed_flow(const struct gossmap *gossmap, struct chan_extra_map *chan_extra_map, struct flow *flow) @@ -122,89 +474,77 @@ void remove_completed_flow(const struct gossmap *gossmap, flow->path[i], flow->dirs[i]); if (!amount_msat_sub(&h->htlc_total, h->htlc_total, flow->amounts[i])) - abort(); + { + debug_err("%s could not substract HTLC amounts, " + "half total htlc amount = %s, " + "flow->amounts[%lld] = %s.", + __PRETTY_FUNCTION__, + type_to_string(tmpctx, struct amount_msat, &h->htlc_total), + i, + type_to_string(tmpctx, struct amount_msat, &flow->amounts[i])); + } if (h->num_htlcs == 0) - abort(); + { + debug_err("%s could not decrease HTLC count.", + __PRETTY_FUNCTION__); + } h->num_htlcs--; } } - -/* Add this to the flow. */ -void flow_add(struct flow *flow, - const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map, - struct amount_msat additional) +// TODO(eduardo): remove this function, is a duplicate +void remove_completed_flow_set( + const struct gossmap *gossmap, + struct chan_extra_map *chan_extra_map, + struct flow **flows) { - struct amount_msat delivered; - - /* Add in new amount */ - if (!amount_msat_add(&delivered, - flow->amounts[tal_count(flow->amounts)-1], additional)) - abort(); - - /* Remove original from current_flows */ - remove_completed_flow(gossmap, chan_extra_map, flow); - - /* Recalc probability and fees, adjust chan_extra_map entries */ - flow_complete(flow, gossmap, chan_extra_map, delivered); + for(size_t i=0;ipath); i++) { + struct chan_extra_half *h = get_chan_extra_half_by_chan(gossmap, + chan_extra_map, + flow->path[i], + flow->dirs[i]); + if (!amount_msat_add(&h->htlc_total, h->htlc_total, flow->amounts[i])) + { + debug_err("%s could not add HTLC amounts, " + "flow->amounts[%lld] = %s.", + __PRETTY_FUNCTION__, + i, + type_to_string(tmpctx, struct amount_msat, &flow->amounts[i])); + } + h->num_htlcs++; + } +} +// TODO(eduardo): remove this function, is a duplicate +void commit_flow_set( + const struct gossmap *gossmap, + struct chan_extra_map *chan_extra_map, + struct flow **flows) +{ + for(size_t i=0;ihalf[dir].proportional_fee - + c->half[dir].base_fee * basefee_penalty) - / 1000000.0; - - /* Feerate term includes delay factor */ - feerate_term = (mu - * (f.millisatoshis /* Raw: costfn */ - * effective_feerate - + c->half[dir].delay * delay_riskfactor)); - - SUPERVERBOSE(" %f + %f = %f\n", - certainty_term, feerate_term, - certainty_term + feerate_term); - return certainty_term + feerate_term; } -/* Helper function to fill in amounts and success_prob for flow */ +/* Helper function to fill in amounts and success_prob for flow + * + * IMPORTANT: here we do not commit flows to chan_extra, flows are commited + * after we send those htlc. + * + * 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. + * */ void flow_complete(struct flow *flow, const struct gossmap *gossmap, struct chan_extra_map *chan_extra_map, @@ -213,40 +553,121 @@ void flow_complete(struct flow *flow, flow->success_prob = 1.0; flow->amounts = tal_arr(flow, struct amount_msat, tal_count(flow->path)); for (int i = tal_count(flow->path) - 1; i >= 0; i--) { - struct chan_extra_half *h; - - h = get_chan_extra_half_by_chan(gossmap, chan_extra_map, - flow->path[i], flow->dirs[i]); - if (!h) { - struct amount_sat cap; - struct amount_msat cap_msat; - - if (!gossmap_chan_get_capacity(gossmap, - flow->path[i], &cap)) - cap = AMOUNT_SAT(0); - if (!amount_sat_to_msat(&cap_msat, cap)) - abort(); - h = new_chan_extra_half(chan_extra_map, - gossmap_chan_scid(gossmap, - flow->path[i]), - flow->dirs[i], - cap_msat); + const struct chan_extra_half *h + = get_chan_extra_half_by_chan(gossmap, + chan_extra_map, + flow->path[i], + flow->dirs[i]); + + if(!h) + { + debug_err("%s unexpected chan_extra_half is NULL", + __PRETTY_FUNCTION__); } - + flow->amounts[i] = delivered; - if (!amount_msat_add(&h->htlc_total, h->htlc_total, delivered)) - abort(); - h->num_htlcs++; flow->success_prob *= edge_probability(h->known_min, h->known_max, - h->htlc_total); + h->htlc_total, + delivered); + if (!amount_msat_add_fee(&delivered, flow_edge(flow, i)->base_fee, flow_edge(flow, i)->proportional_fee)) - abort(); + { + debug_err("%s fee overflow", + __PRETTY_FUNCTION__); + } } } +/* 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]; +}; + +// TODO(eduardo): here chan_extra_map should be const +// TODO(eduardo): here flows should be const +double flow_set_probability( + struct flow ** flows, + struct gossmap const*const gossmap, + struct chan_extra_map * chan_extra_map) +{ + tal_t *this_ctx = tal(tmpctx,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); + + for(size_t i=0;ipath);++j) + { + const struct chan_extra_half *h + = get_chan_extra_half_by_chan( + gossmap, + chan_extra_map, + f->path[j], + f->dirs[j]); + assert(h); + + const u32 c_idx = gossmap_chan_idx(gossmap,f->path[j]); + const int c_dir = f->dirs[j]; + + const struct amount_msat deliver = f->amounts[j]; + + struct amount_msat prev_flow; + if(!amount_msat_add(&prev_flow,h->htlc_total,in_flight[c_idx].half[c_dir])) + { + debug_err("%s (line %d) in-flight amount_msat overflow", + __PRETTY_FUNCTION__, + __LINE__); + } + + prob *= edge_probability(h->known_min,h->known_max, + prev_flow,deliver); + + if(!amount_msat_add(&in_flight[c_idx].half[c_dir], + in_flight[c_idx].half[c_dir], + deliver)) + { + debug_err("%s (line %d) in-flight amount_msat overflow", + __PRETTY_FUNCTION__, + __LINE__); + } + } + } + tal_free(this_ctx); + return prob; +} + static int cmp_amount_msat(const struct amount_msat *a, const struct amount_msat *b, void *unused) @@ -318,8 +739,11 @@ static void get_medians(const struct gossmap *gossmap, if (!num_caps) *median_capacity = amount; else if (!amount_sat_to_msat(median_capacity, caps[num_caps / 2])) - abort(); - + { + debug_err("%s (line %d) amount_msat overflow", + __PRETTY_FUNCTION__, + __LINE__); + } asort(fees, num_fees, cmp_amount_msat, NULL); if (!num_caps) *median_fee = AMOUNT_MSAT(0); @@ -344,6 +768,66 @@ double derive_mu(const struct gossmap *gossmap, / (median_fee.millisatoshis + 1); /* Raw: derive_mu */ } +/* 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 CTLV delay. + * */ +s64 linear_fee_cost( + const struct gossmap_chan *c, + const int dir, + double base_fee_penalty, + double delay_feefactor) +{ + 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; +} + +struct amount_msat flow_set_fee(struct flow **flows) +{ + struct amount_msat fee = AMOUNT_MSAT(0); + + for (size_t i = 0; i < tal_count(flows); i++) { + struct amount_msat this_fee; + size_t n = tal_count(flows[i]->amounts); + + if (!amount_msat_sub(&this_fee, + flows[i]->amounts[0], + flows[i]->amounts[n-1])) + { + debug_err("%s (line %d) amount_msat overflow", + __PRETTY_FUNCTION__, + __LINE__); + } + if(!amount_msat_add(&fee, this_fee,fee)) + { + debug_err("%s (line %d) amount_msat overflow", + __PRETTY_FUNCTION__, + __LINE__); + } + } + return fee; +} + +/* Helper to access the half chan at flow index idx */ +const struct half_chan *flow_edge(const struct flow *flow, size_t idx) +{ + assert(idx < tal_count(flow->path)); + return &flow->path[idx]->half[flow->dirs[idx]]; +} + #ifndef SUPERVERBOSE_ENABLED #undef SUPERVERBOSE #endif diff --git a/plugins/renepay/flow.h b/plugins/renepay/flow.h index d5bbaf8e8152..06bffe154b21 100644 --- a/plugins/renepay/flow.h +++ b/plugins/renepay/flow.h @@ -4,12 +4,29 @@ #include #include #include +#include +#include + +// TODO(eduardo): 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_CAP (AMOUNT_MSAT(21000000*MSAT_PER_BTC)) /* 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 */ @@ -23,6 +40,8 @@ struct chan_extra { } half[2]; }; +bool chan_extra_is_busy(struct chan_extra const * const ce); + static inline const struct short_channel_id chan_extra_scid(const struct chan_extra *cd) { @@ -48,25 +67,140 @@ HTABLE_DEFINE_TYPE(struct chan_extra, 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); + +/* 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); + + +/* This helper function preserves the uncertainty network invariant after the + * knowledge is updated. It assumes that the (channel,!dir) knowledge is + * correct. */ +void chan_extra_adjust_half(struct chan_extra *ce, + int dir); + +/* Helper to find the min of two amounts */ +static inline struct amount_msat amount_msat_min( + struct amount_msat a, + struct amount_msat b) +{ + return a.millisatoshis < b.millisatoshis ? a : b; +} +/* Helper to find the max of two amounts */ +static inline struct amount_msat amount_msat_max( + struct amount_msat a, + struct amount_msat b) +{ + return a.millisatoshis > b.millisatoshis ? a : b; +} + +/* Update the knowledge that this (channel,direction) can send x msat.*/ +void chan_extra_can_send(struct chan_extra_map *chan_extra_map, + struct short_channel_id scid, + int dir, + struct amount_msat x); + +/* Update the knowledge that this (channel,direction) cannot send x msat.*/ +void chan_extra_cannot_send(struct payment* p, + struct chan_extra_map *chan_extra_map, + struct short_channel_id scid, + int dir, + struct amount_msat x); + +/* Update the knowledge that this (channel,direction) has liquidity x.*/ +void chan_extra_set_liquidity(struct chan_extra_map *chan_extra_map, + struct short_channel_id scid, + int dir, + struct amount_msat x); + +/* Update the knowledge that this (channel,direction) has sent x msat.*/ +void chan_extra_sent_success(struct chan_extra_map *chan_extra_map, + struct short_channel_id scid, + int dir, + struct amount_msat x); + +/* Forget a bit about this (channel,direction) state. */ +void chan_extra_relax(struct chan_extra_map *chan_extra_map, + struct short_channel_id scid, + int dir, + struct amount_msat down, + struct amount_msat up); + + +/* 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 scid, int dir); +/* 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); -/* tal_free() this removes it from chan_extra_map */ -struct chan_extra_half *new_chan_extra_half(struct chan_extra_map *chan_extra_map, - const struct short_channel_id scid, - int dir, - struct amount_msat capacity); - /* An actual partial flow. */ struct flow { - /* The series of channels to traverse. */ - const struct gossmap_chan **path; + struct gossmap_chan const **path; /* The directions to traverse. */ int *dirs; /* Amounts for this flow (fees mean this shrinks across path). */ @@ -78,18 +212,6 @@ struct flow { /* Helper to access the half chan at flow index idx */ const struct half_chan *flow_edge(const struct flow *flow, size_t idx); -/* Path comparison helper */ -bool flow_path_eq(const struct gossmap_chan **path1, - const int *dirs1, - const struct gossmap_chan **path2, - const int *dirs2); - -/* Add this to the completed flow. */ -void flow_add(struct flow *flow, - const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map, - struct amount_msat additional); - /* A big number, meaning "don't bother" (not infinite, since you may add) */ #define FLOW_INF_COST 100000000.0 @@ -105,17 +227,31 @@ double flow_edge_cost(const struct gossmap *gossmap, double basefee_penalty, double delay_riskfactor); -/* Function to fill in amounts and success_prob for flow, and add to - * chan_extra_map */ +/* Function to fill in amounts and success_prob for flow. */ void flow_complete(struct flow *flow, const struct gossmap *gossmap, struct chan_extra_map *chan_extra_map, struct amount_msat delivered); +/* Compute the prob. of success of a set of concurrent set of flows. */ +double flow_set_probability( + struct flow ** flows, + struct gossmap const*const gossmap, + struct chan_extra_map * chan_extra_map); + +// TODO(eduardo): we probably don't need this. Instead we should have payflow +// input. /* Once flow is completed, this can remove it from the extra_map */ void remove_completed_flow(const struct gossmap *gossmap, struct chan_extra_map *chan_extra_map, struct flow *flow); +// TODO(eduardo): we probably don't need this. Instead we should have payflow +// input. +void remove_completed_flow_set(const struct gossmap *gossmap, + struct chan_extra_map *chan_extra_map, + struct flow **flows); + +struct amount_msat flow_set_fee(struct flow **flows); /* * mu (μ) is used as follows in the cost function: @@ -158,4 +294,26 @@ double derive_mu(const struct gossmap *gossmap, struct amount_msat amount, double frugality); +s64 linear_fee_cost( + const struct gossmap_chan *c, + const int dir, + double base_fee_penalty, + double delay_feefactor); + +// TODO(eduardo): we probably don't need this. Instead we should have payflow +// input. +/* Take the flows and commit them to the chan_extra's . */ +void commit_flow( + const struct gossmap *gossmap, + struct chan_extra_map *chan_extra_map, + struct flow *flow); + +// TODO(eduardo): we probably don't need this. Instead we should have payflow +// input. +/* Take the flows and commit them to the chan_extra's . */ +void commit_flow_set( + const struct gossmap *gossmap, + struct chan_extra_map *chan_extra_map, + struct flow **flows); + #endif /* LIGHTNING_PLUGINS_RENEPAY_FLOW_H */ diff --git a/plugins/renepay/heap.h b/plugins/renepay/heap.h new file mode 100644 index 000000000000..49d81fda49d0 --- /dev/null +++ b/plugins/renepay/heap.h @@ -0,0 +1,195 @@ +#ifndef LIGHTNING_PLUGINS_RENEPAY_HEAP_H +#define LIGHTNING_PLUGINS_RENEPAY_HEAP_H + +#include + + +/* A functionality missing in gheap that can be used to update elements. + * Input: item + * Output: the position of the smallest element p, such is greater equal item. + * Formally: + * Let X={x in heap: !(x +#include + + +static int less_comparer(const void *const ctx UNUSED, + const void *const a, + const void *const b) +{ + s64 da = ((struct heap_data*)a)->distance, + db = ((struct heap_data*)b)->distance; + u32 ia = ((struct heap_data*)a)->idx, + ib = ((struct heap_data*)b)->idx; + return da==db ? ia > ib : da > db; +} + +static void item_mover(void *const dst, const void *const src) +{ + *(struct heap_data*)dst = *(struct heap_data*)src; +} + +struct heap* heap_new(const tal_t *ctx, const size_t max_capacity) +{ + struct heap* heap = tal(ctx,struct heap); + heap->size=0; + heap->data = tal_arr(heap,struct heap_data,max_capacity); + heap->max_size = max_capacity; + + heap->gheap_ctx.fanout=2; + heap->gheap_ctx.page_chunks=1; + heap->gheap_ctx.item_size= sizeof(struct heap_data); + heap->gheap_ctx.less_comparer=less_comparer; + heap->gheap_ctx.less_comparer_ctx=heap; + heap->gheap_ctx.item_mover=item_mover; + + return heap; +} + + +void heap_insert(struct heap* heap, u32 idx, s64 distance) +{ + heap->data[heap->size].idx=idx; + heap->data[heap->size].distance=distance; + heap->size++; + + assert(heap->size<=heap->max_size); + + gheap_restore_heap_after_item_increase(&heap->gheap_ctx, + heap->data, + heap->size, + heap->size-1); +} +bool heap_empty(const struct heap* heap) +{ + return heap->size==0; +} +struct heap_data * heap_top(const struct heap * heap) +{ + return &heap->data[0]; +} +void heap_pop(struct heap* heap) +{ + if(heap->size>0) + gheap_pop_heap(&heap->gheap_ctx,heap->data,heap->size--); +} + +/* Input: item + * Output: the smallest x such that !(xfanout; + const size_t item_size = ctx->item_size; + const void*const less_comparer_ctx = ctx->less_comparer_ctx; + const gheap_less_comparer_t less_comparer = ctx->less_comparer; + + if(less_comparer(less_comparer_ctx,base,item)) + { + // root=heap_size) + break; + if(!less_comparer(less_comparer_ctx, + ((char*)base) + child*item_size, + item)) + { + // satisfies the condition, + // is it the smallest one? + if(!less_comparer(less_comparer_ctx, + ((char*)base) + best_child*item_size, + ((char*)base) + child*item_size)) + { + // child <= best_child, so child is a + // better upper bound + best_child = child; + } + } + } + + if(best_child==last) + { + // no change, we stop + break; + } + last = best_child; + } + return last; +} +void heap_update(struct heap* heap, u32 idx, s64 old_distance, s64 new_distance) +{ + const gheap_less_comparer_t less_comparer = heap->gheap_ctx.less_comparer; + const void *const less_comparer_ctx = heap->gheap_ctx.less_comparer_ctx; + + struct heap_data old_item = (struct heap_data){.idx=idx, .distance=old_distance}; + + size_t pos = gheap_upper_bound(&heap->gheap_ctx,heap->data,heap->size,&old_item); + if(pos>=heap->size || heap->data[pos].idx!=idx) + { + heap_insert(heap,idx,new_distance); + } + else + { + struct heap_data new_item = (struct heap_data){.idx=idx, .distance=new_distance}; + + if(less_comparer(less_comparer_ctx,&new_item,&heap->data[pos])) + { + heap->data[pos].distance = new_distance; + gheap_restore_heap_after_item_decrease( + &heap->gheap_ctx, + heap->data, + heap->size, + pos); + }else + { + heap->data[pos].distance = new_distance; + gheap_restore_heap_after_item_increase( + &heap->gheap_ctx, + heap->data, + heap->size, + pos); + } + } +} + +#endif /* LIGHTNING_PLUGINS_RENEPAY_HEAP_H */ diff --git a/plugins/renepay/mcf.c b/plugins/renepay/mcf.c new file mode 100644 index 000000000000..35fdd98f5c51 --- /dev/null +++ b/plugins/renepay/mcf.c @@ -0,0 +1,1508 @@ +#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}; + +// how many bits for linearization parts plus 1 bit for the direction of the +// channel plus 1 bit for the dual representation. +static const size_t ARC_ADDITIONAL_BITS = PARTS_BITS + 2; + +static const s64 INFINITE = INT64_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. + * + * */ +typedef union +{ + struct{ + u32 dual: 1; + u32 part: PARTS_BITS; + u32 chandir: 1; + u32 chanidx: (32-1-PARTS_BITS-1); + }; + u32 idx; +} arc_t; + +#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; + struct gossmap_node const*source; + struct gossmap_node const*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 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) + + arc_t *node_adjacency_next_arc; + arc_t *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 arc_t arc_dual(arc_t arc) +{ + arc.dual ^= 1; + return arc; +} +/* Helper function. */ +static bool arc_is_dual(const arc_t arc) +{ + return arc.dual == 1; +} + +/* 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 arc_t 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 arc_t 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 arc_t arc) +{ + const arc_t 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 arc_t 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 arc_t 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 arc_t node_adjacency_next( + const struct linear_network *linear_network, + const arc_t arc) +{ + assert(arc.idx < tal_count(linear_network->node_adjacency_next_arc)); + return linear_network->node_adjacency_next_arc[arc.idx]; +} + +/* Helper function. + * Given a channel index, we should be able to deduce the arc id. */ +static arc_t channel_idx_to_arc( + const u32 chan_idx, + int half, + int part, + int dual) +{ + arc_t arc; + // arc.idx=0; // shouldn't be necessary, but valgrind complains of uninitialized field idx + arc.dual=dual; + arc.part=part; + arc.chandir=half; + arc.chanidx = chan_idx; + return arc; +} + +// TODO(eduardo): unit test this +/* Split a directed channel into parts with linear cost function. */ +static void 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) + { + debug_err("%s (line %d) unexpected, extra_half is NULL", + __PRETTY_FUNCTION__, + __LINE__); + } + + s64 a = extra_half->known_min.millisatoshis/1000, + b = 1 + extra_half->known_max.millisatoshis/1000; + + capacity[0]=a; + cost[0]=0; + for(size_t i=1;icap_fraction[i]*(b-a); + + cost[i] = params->cost_fraction[i] + *params->amount.millisatoshis + *params->prob_cost_factor*1.0/(b-a); + } +} + +static void alloc_residual_netork( + 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; + + 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); +} +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(u32 idx=0;idxcap[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(u32 arc_idx=0;arc_idxmax_num_arcs;++arc_idx) + { + arc_t arc = (arc_t){.idx=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 arc_t 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 arc_t 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; +} + + +static void init_linear_network( + const struct pay_parameters *params, + struct linear_network *linear_network) +{ + const size_t max_num_chans = gossmap_max_chan_idx(params->gossmap); + const size_t max_num_arcs = max_num_chans << ARC_ADDITIONAL_BITS; + 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); + 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,arc_t,max_num_arcs); + 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,arc_t,max_num_nodes); + 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); + 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); + 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); + + 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); + + // TODO(eduardo): in which case can this be triggered? + if (!gossmap_chan_set(c,half)) + continue; + + const u32 chan_id = gossmap_chan_idx(params->gossmap, c); + + if (params->disabled && bitmap_test_bit(params->disabled,chan_id)) + continue; + + + 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` + linearize_channel(params,c,half,capacity,prob_cost); + + 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 + arc_t 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; + } + } + } +} + +/* 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. + * Returns RENEPAY_ERR_OK if the path exists. */ +static int find_admissible_path( + const struct linear_network *linear_network, + const struct residual_network *residual_network, + const u32 source, + const u32 target, + arc_t *prev) +{ + tal_t *this_ctx = tal(tmpctx,tal_t); + + int ret = RENEPAY_ERR_NOFEASIBLEFLOW; + 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) + { + ret = RENEPAY_ERR_OK; + break; + } + + for(arc_t 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(tmpctx,struct queue_data); + qdata->idx = next; + lqueue_enqueue(&myqueue,qdata); + } + } + tal_free(this_ctx); + return ret; +} + +/* 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 arc_t *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 arc_t *prev, + s64 flow) +{ + u32 cur = target; + + while(cur!=source) + { + assert(cur < tal_count(prev)); + const arc_t arc = prev[cur]; + const arc_t 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 int find_feasible_flow( + const struct linear_network *linear_network, + struct residual_network *residual_network, + const u32 source, + const u32 target, + s64 amount) +{ + assert(amount>=0); + + tal_t *this_ctx = tal(tmpctx,tal_t); + int ret = RENEPAY_ERR_OK; + + /* path information + * prev: is the id of the arc that lead to the node. */ + arc_t *prev = tal_arr(this_ctx,arc_t,linear_network->max_num_nodes); + + while(amount>0) + { + // find a path from source to target + int err = find_admissible_path( + linear_network, + residual_network,source,target,prev); + + if(err!=RENEPAY_ERR_OK) + { + ret = RENEPAY_ERR_NOFEASIBLEFLOW; + break; + } + + // 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); + augment_flow(linear_network,residual_network,source,target,prev,delta); + + assert(delta>0 && delta<=amount); + amount -= delta; + } + + tal_free(this_ctx); + return ret; +} + +// 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 int find_optimal_path( + const struct linear_network *linear_network, + const struct residual_network* residual_network, + const u32 source, + const u32 target, + arc_t *prev) +{ + tal_t *this_ctx = tal(tmpctx,tal_t); + int ret = RENEPAY_ERR_NOFEASIBLEFLOW; + + bitmap *visited = tal_arrz(this_ctx, bitmap, + BITMAP_NWORDS(linear_network->max_num_nodes)); + + 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(next,distance[cur]+cij); + prev[next]=arc; + } + } + tal_free(this_ctx); + return ret; +} + +/* 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(arc_t arc=node_adjacency_begin(linear_network,node); + !node_adjacency_end(arc); + arc = node_adjacency_next(linear_network,arc)) + { + if(arc_is_dual(arc))continue; + + arc_t 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 int optimize_mcf( + const struct linear_network *linear_network, + struct residual_network *residual_network, + const u32 source, + const u32 target, + const s64 amount) +{ + assert(amount>=0); + tal_t *this_ctx = tal(tmpctx,tal_t); + + int ret = RENEPAY_ERR_OK; + + zero_flow(linear_network,residual_network); + arc_t *prev = tal_arr(this_ctx,arc_t,linear_network->max_num_nodes); + + s64 const*const distance = dijkstra_distance_data(); + + s64 remaining_amount = amount; + + while(remaining_amount>0) + { + int err = find_optimal_path(linear_network,residual_network,source,target,prev); + if(err!=RENEPAY_ERR_OK) + { + // unexpected error + ret = RENEPAY_ERR_NOFEASIBLEFLOW; + break; + } + + // 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); + augment_flow(linear_network,residual_network,source,target,prev,delta); + + assert(delta>0 && delta<=remaining_amount); + 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 ret; +} + +// 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 struct chan_flow *chan_flow, + const u32 start_idx, + const s64 *balance, + + struct gossmap_chan const** 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",__PRETTY_FUNCTION__,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; + struct gossmap_chan const *c + = gossmap_nth_chan(gossmap, + cur,i,&dir); + + if (!gossmap_chan_set(c,dir)) + 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; +}; + +// TODO(eduardo): check this +/* 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, + + // 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) +{ + assert(excess.millisatoshis < 1000); + + tal_t *this_ctx = tal(tmpctx,tal_t); + + 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); + + struct gossmap_chan const **prev_chan + = tal_arr(this_ctx,struct gossmap_chan const*,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); + + // Convert the arc based residual network flow into a flow in the + // directed channel network. + // Compute balance on the nodes. + for(u32 n = 0;npath = tal_arr(fp,struct gossmap_chan const*,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]; + struct gossmap_chan const * 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)) + { + debug_err("%s (line %d) unable to substract excess.", + __PRETTY_FUNCTION__, + __LINE__); + } + excess = amount_msat(0); + + // complete the flow path by adding real fees and + // probabilities. + flow_complete(fp,gossmap,chan_extra_map,delivered); + + // add fp to list + ld = tal(list_ctx,struct list_data); + ld->flow_path = fp; + list_add(&path_list,&ld->list); + num_paths++; + } + } + + // copy the list into the array we are going to return + struct flow **flows = tal_arr(ctx,struct flow*,num_paths); + size_t pos=0; + list_for_each(&path_list,ld,list) + { + flows[pos++] = tal_steal(flows,ld->flow_path); + } + + tal_free(this_ctx); + return flows; +} + +/* Given the constraints on max fee and min prob., + * is the flow A better than B? */ +static bool is_better( + struct amount_msat max_fee, + double min_probability, + + struct amount_msat A_fee, + double A_prob, + + struct amount_msat B_fee, + double B_prob) +{ + bool A_fee_pass = amount_msat_less_eq(A_fee,max_fee); + bool B_fee_pass = amount_msat_less_eq(B_fee,max_fee); + bool A_prob_pass = A_prob >= 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); +} + + +// 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 delay_feefactor, + double base_fee_penalty, + u32 prob_cost_factor ) +{ + tal_t *this_ctx = tal(tmpctx,tal_t); + + struct pay_parameters *params = tal(this_ctx,struct pay_parameters); + + params->gossmap = gossmap; + params->source = source; + params->target = target; + params->chan_extra_map = chan_extra_map; + + params->disabled = disabled; + assert(!disabled + || tal_bytelen(disabled) == bitmap_sizeof(gossmap_max_chan_idx(gossmap))); + + params->amount = amount; + + // template the channel partition into linear arcs + params->cap_fraction[0]=0; + params->cost_fraction[0]=0; + for(size_t i =0;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; + + // build the uncertainty network with linearization and residual arcs + struct linear_network *linear_network= tal(this_ctx,struct linear_network); + init_linear_network(params,linear_network); + + struct residual_network *residual_network = tal(this_ctx,struct residual_network); + alloc_residual_netork(linear_network,residual_network); + + dijkstra_malloc(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; + struct flow **best_flow_paths = NULL; + + /* 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; + const u64 pay_amount_sats = params->amount.millisatoshis/1000 + + (pay_amount_msats ? 1 : 0); + const struct amount_msat excess + = amount_msat(pay_amount_msats ? 1000 - pay_amount_msats : 0); + + int err = find_feasible_flow(linear_network,residual_network,source_idx,target_idx, + pay_amount_sats); + + if(err!=RENEPAY_ERR_OK) + { + // there is no flow that satisfy the constraints, we stop here + goto finish; + } + + // first flow found + best_flow_paths = get_flow_paths(ctx,params->gossmap,params->chan_extra_map, + linear_network,residual_network, + excess); + best_prob_success = flow_set_probability(best_flow_paths, + params->gossmap, + params->chan_extra_map); + best_fee = flow_set_fee(best_flow_paths); + + // 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->chan_extra_map, + linear_network,residual_network, + excess); + + double prob_success = flow_set_probability( + flow_paths, + params->gossmap, + params->chan_extra_map); + struct amount_msat fee = flow_set_fee(flow_paths); + + // 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_steal(ctx,flow_paths); + best_fee = fee; + best_prob_success=prob_success; + flow_paths = NULL; + } + + 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; + } + + if(flow_paths) + tal_free(flow_paths); + } + + + + finish: + + tal_free(this_ctx); + return best_flow_paths; +} + diff --git a/plugins/renepay/mcf.h b/plugins/renepay/mcf.h index 0a2d73c798e1..009165646744 100644 --- a/plugins/renepay/mcf.h +++ b/plugins/renepay/mcf.h @@ -1,11 +1,25 @@ #ifndef LIGHTNING_PLUGINS_RENEPAY_MCF_H #define LIGHTNING_PLUGINS_RENEPAY_MCF_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 +}; + + + /** - * minflow - API for min cost flow function(s). + * 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 @@ -13,24 +27,44 @@ struct chan_extra_map; * @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 - * @frugality: how important fees are compared to certainty (0.1 = certain, 10 = fees) - * @delay_feefactor: how important delays are compared to fees - * + * + * @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 CTLV 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 * - * @mu converts fees to add it to the uncertainty term. + * @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, - double frugality, - double delay_feefactor); +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 delay_feefactor, + double base_fee_penalty, + u32 prob_cost_factor); #endif /* LIGHTNING_PLUGINS_RENEPAY_MCF_H */ diff --git a/plugins/renepay/not_mcf.c b/plugins/renepay/not_mcf.c deleted file mode 100644 index 88cea6556b7b..000000000000 --- a/plugins/renepay/not_mcf.c +++ /dev/null @@ -1,485 +0,0 @@ -/* Horrible approximation to Min Cost Flow. - * - * i.e. https://arxiv.org/abs/2107.05322 - * - * But the best solution for Min Cost Flow is approximate, and almost - * linear in the number of edges: https://arxiv.org/abs/2203.00671 - * - * It is, however, O(🤯) in implementation time :) I welcome anyone - * who wants to implement it, and they can compare themselves to this - * gross implementation with pride. - */ -/* Without this, gheap is *really* slow! Comment out for debugging. */ -#define NDEBUG -#include "config.h" -#include -#include -#include -#include -#include -#include -#include - -#ifndef SUPERVERBOSE -#define SUPERVERBOSE(...) -#else -#define SUPERVERBOSE_ENABLED 1 -#endif - -#define ASSERT(x) do { if (!(x)) abort(); } while(0) - -/* Each node has this side-info. */ -struct dijkstra { - /* Using our cost function */ - double cost; - /* I want to use an index here, except that gheap moves things onto - * a temporary on the stack and that makes things complex. */ - /* NULL means it's been visited already. */ - const struct gossmap_node **heapptr; - /* We could re-evaluate to determine this, but keeps it simple */ - struct gossmap_chan *best_chan; -}; - -struct pay_parameters { - /* The gossmap we are using */ - struct gossmap *gossmap; - /* Our array of dijkstra per-node info */ - struct dijkstra *dij; - /* How much do we care about fees vs certainty? */ - double mu; - /* How much do we care about locktimes? */ - double delay_feefactor; - /* Penalty factor for having a basefee (adds to ppm). */ - double basefee_penalty; - /* Optional bitarray of disabled chans. */ - const bitmap *disabled; - - /* The working heap */ - struct gheap_ctx gheap_ctx; - const struct gossmap_node **heap; - size_t heapsize; - - /* Extra information we intuited about the channels */ - struct chan_extra_map *chan_extra_map; -}; - -/* Required global for gheap less_comparer */ -static struct pay_parameters *global_params; - -static struct dijkstra *get_dijkstra(const struct pay_parameters *params, - const struct gossmap_node *n) -{ - return params->dij + gossmap_node_idx(params->gossmap, n); -} - -/* We want a minheap, not a maxheap, so this is backwards! */ -static int less_comparer(const void *const ctx, - const void *const a, - const void *const b) -{ - const struct pay_parameters *params = ctx; - return get_dijkstra(params, *(struct gossmap_node **)a)->cost - > get_dijkstra(params, *(struct gossmap_node **)b)->cost; -} - -static void item_mover(void *const dst, const void *const src) -{ - const struct pay_parameters *params = global_params; - struct gossmap_node *n = *((struct gossmap_node **)src); - get_dijkstra(params, n)->heapptr = dst; - *((struct gossmap_node **)dst) = n; -} - -static void append_to_heap(struct pay_parameters *params, - const struct gossmap_node *n, - double cost) -{ - struct dijkstra *d = get_dijkstra(params, n); - - params->heap[params->heapsize] = n; - d->heapptr = ¶ms->heap[params->heapsize]; - d->cost = cost; - params->heapsize++; -} - -static void init_heap(struct pay_parameters *params, - const struct gossmap_node *target) -{ - /* Initialize all heapptrs to NULL, costs to infinite */ - for (size_t i = 0; i < tal_count(params->dij); i++) { - params->dij[i].cost = FLOW_INF_COST; - params->dij[i].heapptr = NULL; - } - - /* First entry in heap is start, cost 0 */ - params->heapsize = 0; - append_to_heap(params, target, 0); - - ASSERT(gheap_is_heap(¶ms->gheap_ctx, params->heap, params->heapsize)); -} - -/* Convenient wrapper for flow_edge_cost */ -static double costfn(const struct pay_parameters *params, - const struct gossmap_chan *c, int dir, - struct amount_msat flow) -{ - struct amount_msat min, max, prev_flow; - struct chan_extra_half *h = get_chan_extra_half_by_chan(params->gossmap, - params->chan_extra_map, - c, dir); - if (h) { - SUPERVERBOSE("costfn found extra %p: cap %s-%s, flow %s\n", - h, - type_to_string(tmpctx, struct amount_msat, &h->known_min), - type_to_string(tmpctx, struct amount_msat, &h->known_max), - type_to_string(tmpctx, struct amount_msat, &h->htlc_total)); - min = h->known_min; - max = h->known_max; - prev_flow = h->htlc_total; - } else { - /* We know nothing, use 0 - capacity */ - struct amount_sat cap; - - min = AMOUNT_MSAT(0); - if (!gossmap_chan_get_capacity(params->gossmap, c, &cap)) - cap = AMOUNT_SAT(0); - if (!amount_sat_to_msat(&max, cap)) - abort(); - prev_flow = AMOUNT_MSAT(0); - } - - return flow_edge_cost(params->gossmap, - c, dir, - min, max, prev_flow, - flow, - params->mu, - params->basefee_penalty, - params->delay_feefactor); -} - -static bool run_shortest_path(struct pay_parameters *params, - const struct gossmap_node *source, - const struct gossmap_node *target, - struct amount_msat flow) -{ - init_heap(params, target); - - while (params->heapsize != 0) { - struct dijkstra *cur_d; - const struct gossmap_node *cur; - - /* Pop off top of heap */ - cur = params->heap[0]; - - /* Reached the source? Done! */ - if (cur == source) - return true; - - cur_d = get_dijkstra(params, cur); - ASSERT(cur_d->heapptr == params->heap); - gheap_pop_heap(¶ms->gheap_ctx, params->heap, params->heapsize--); - cur_d->heapptr = NULL; - - for (size_t i = 0; i < cur->num_chans; i++) { - struct gossmap_node *neighbor; - int which_half; - struct gossmap_chan *c; - struct dijkstra *d; - double cost; - - c = gossmap_nth_chan(params->gossmap, - cur, i, &which_half); - /* We're going to traverse backwards, so we need - * channel_update into this node */ - if (!gossmap_chan_set(c, !which_half)) - continue; - - neighbor = gossmap_nth_node(params->gossmap, - c, !which_half); - - d = get_dijkstra(params, neighbor); - /* Ignore if already visited. */ - if (d->cost != FLOW_INF_COST && !d->heapptr) - continue; - - /* They can explicitly disable some channels for this - * run */ - if (params->disabled - && bitmap_test_bit(params->disabled, - gossmap_chan_idx(params->gossmap, - c))) - continue; - - /* Get edge cost (will be FLOW_INF_COST in - * insufficient cpacity) */ - cost = costfn(params, c, !which_half, flow); - - /* If that doesn't give us a cheaper path, ignore */ - if (cur_d->cost + cost >= d->cost) - continue; - - /* Yay, we have a new winner! */ - d->cost = cur_d->cost + cost; - d->best_chan = c; - if (!d->heapptr) - append_to_heap(params, neighbor, d->cost); - - gheap_restore_heap_after_item_increase(¶ms->gheap_ctx, - params->heap, - params->heapsize, - d->heapptr - - params->heap); - } - } - return false; -} - -/* Which dir for the channel is n? */ -static int gossmap_dir(const struct gossmap *gossmap, - const struct gossmap_node *n, - const struct gossmap_chan *c) -{ - /* Get the *other* peer attached to this channel */ - if (gossmap_nth_node(gossmap, c, 0) == n) - return 0; - ASSERT(gossmap_nth_node(gossmap, c, 1) == n); - return 1; -} - -/* We find the cheapest path between source and target for flow, ignoring - * capacity limits. */ -static const struct gossmap_chan ** -find_cheap_flow(const tal_t *ctx, - struct pay_parameters *params, - const struct gossmap_node *source, - const struct gossmap_node *target, - struct amount_msat flow, - int **dirs) -{ - const struct gossmap_node *n; - const struct gossmap_chan **arr; - - if (!run_shortest_path(params, source, target, flow)) - return NULL; - - arr = tal_arr(ctx, const struct gossmap_chan *, 0); - *dirs = tal_arr(ctx, int, 0); - n = source; - while (n != target) { - struct dijkstra *d = get_dijkstra(params, n); - int dir; - - dir = gossmap_dir(params->gossmap, n, d->best_chan); - tal_arr_expand(&arr, d->best_chan); - tal_arr_expand(dirs, dir); - - /* Get other end of channel */ - n = gossmap_nth_node(params->gossmap, d->best_chan, !dir); - } - return arr; -} - -static void print_path(const char *desc, - const struct pay_parameters *params, - const struct gossmap_chan **path, - const int *dirs, - struct amount_msat amt) -{ -#ifdef SUPERVERBOSE_ENABLED - SUPERVERBOSE("%s: ", desc); - - for (size_t i = 0; i < tal_count(path); i++) { - struct short_channel_id scid - = gossmap_chan_scid(params->gossmap, path[i]); - double cost; - - print_enable = false; - cost = costfn(params, path[i], dirs[i], amt); - print_enable = true; - SUPERVERBOSE("%s%s/%i(set=%i/%i,htlc_max=%"PRIu64"/rev:%"PRIu64",fee=%u+%u,cost=%f) ", - i ? "->" : "", - type_to_string(tmpctx, struct short_channel_id, &scid), - dirs[i], - gossmap_chan_set(path[i], 0), - gossmap_chan_set(path[i], 1), - fp16_to_u64(path[i]->half[dirs[i]].htlc_max), - fp16_to_u64(path[i]->half[!dirs[i]].htlc_max), - path[i]->half[dirs[i]].base_fee, - path[i]->half[dirs[i]].proportional_fee, - cost); - } - SUPERVERBOSE("\n"); -#endif /* SUPERVERBOSE_ENABLED */ -} - -static void print_flow(const char *desc, - const struct pay_parameters *params, - const struct flow *flow) -{ - struct amount_msat fee, delivered; - - delivered = flow->amounts[tal_count(flow->amounts)-1]; - print_path(desc, params, flow->path, flow->dirs, delivered); - if (!amount_msat_sub(&fee, flow->amounts[0], delivered)) - abort(); - SUPERVERBOSE(" prob %.2f, %s delivered with fee %s\n", - flow->success_prob, - type_to_string(tmpctx, struct amount_msat, &delivered), - type_to_string(tmpctx, struct amount_msat, &fee)); -} - -static double flow_cost(struct pay_parameters *params, - const struct gossmap_chan **path, - const int *dirs, - struct amount_msat amount) -{ - double cost = 0; - - for (size_t i = 0; i < tal_count(path); i++) - cost += costfn(params, path[i], dirs[i], amount); - return cost; -} - -static struct flow *find_similar_flow(struct pay_parameters *params, - struct flow **flows, size_t num_flows, - const struct gossmap_chan **path, - const int *dirs, - struct amount_msat amount) -{ - double best_cost; - struct flow *best; - - /* First search for exact equal */ - for (size_t i = 0; i < num_flows; i++) { - if (flow_path_eq(flows[i]->path, flows[i]->dirs, path, dirs)) - return flows[i]; - } - - /* OK, see if one of the existing paths is similar cost. */ - best_cost = flow_cost(params, path, dirs, amount) * 1.10; - best = NULL; - for (size_t i = 0; i < num_flows; i++) { - double cost = flow_cost(params, flows[i]->path, flows[i]->dirs, amount); - if (cost < best_cost) { - best = flows[i]; - best_cost = cost; - } - } - return best; -} - -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, - double frugality, - double delay_feefactor) -{ - struct pay_parameters *params = tal(tmpctx, struct pay_parameters); - struct flow **flows; - const struct amount_msat min_flow = AMOUNT_MSAT(5000); - struct amount_msat step, remaining; - size_t num_flows; - - params->gossmap = gossmap; - params->dij = tal_arr(params, struct dijkstra, - gossmap_max_node_idx(gossmap)); - - params->mu = derive_mu(gossmap, amount, frugality); - params->delay_feefactor = delay_feefactor; - params->disabled = disabled; - ASSERT(!disabled - || tal_bytelen(disabled) == bitmap_sizeof(gossmap_max_chan_idx(gossmap))); - - /* If we assume we split into 5 parts, adjust ppm on basefee to - * make be correct at amt / 5. Thus 1 basefee is worth 1 ppm at - * amount / 5. */ - params->basefee_penalty - = 5000000.0 / amount.millisatoshis; /* Raw: penalty */ - - /* There doesn't seem to be much difference with fanout 2-4. */ - params->gheap_ctx.fanout = 2; - /* There seems to be a slight decrease if we alter this value. */ - params->gheap_ctx.page_chunks = 1; - params->gheap_ctx.item_size = sizeof(*params->heap); - params->gheap_ctx.less_comparer = less_comparer; - params->gheap_ctx.less_comparer_ctx = params; - params->gheap_ctx.item_mover = item_mover; - - /* This is initialized (and heapsize set) in init_heap each time */ - params->heap = tal_arr(params, const struct gossmap_node *, - gossmap_num_nodes(gossmap)); - - params->chan_extra_map = chan_extra_map; - global_params = params; - - /* Now gather the flows: we randomize step a little, but aim for 50. */ - flows = tal_arr(ctx, struct flow *, 100); - step = amount_msat_div(amount, 50); - if (amount_msat_less(step, min_flow)) - step = min_flow; - - remaining = amount; - num_flows = 0; - while (amount_msat_greater(remaining, AMOUNT_MSAT(0))) { - struct amount_msat this_amount; - const struct gossmap_chan **path; - int *dirs; - struct flow *flow; - - /* Randomize amount a little */ - if (!amount_msat_scale(&this_amount, step, - 0.8 + 0.4 * pseudorand_double())) - abort(); - - if (amount_msat_greater(this_amount, remaining)) - this_amount = remaining; - - path = find_cheap_flow(tmpctx, params, source, target, - this_amount, &dirs); - if (!path) - return tal_free(flows); - - /* Maybe add to existing. */ - flow = find_similar_flow(params, flows, num_flows, path, dirs, this_amount); - if (flow) { - struct amount_msat before, after, tot; - - before = flow->amounts[tal_count(flow->amounts)-1]; - print_flow("Duplicate flow before", params, flow); - flow_add(flow, params->gossmap, params->chan_extra_map, - this_amount); - print_flow("Duplicate flow after", params, flow); - after = flow->amounts[tal_count(flow->amounts)-1]; - if (!amount_msat_sub(&tot, after, before)) - abort(); - ASSERT(amount_msat_eq(tot, this_amount)); - } else { - flow = tal(flows, struct flow); - flow->path = tal_steal(flow, path); - flow->dirs = tal_steal(flow, dirs); - flow_complete(flow, params->gossmap, - params->chan_extra_map, - this_amount); - print_flow("New flow", params, flow); - flows[num_flows++] = flow; - ASSERT(amount_msat_eq(flow->amounts[tal_count(flow->amounts)-1], - this_amount)); - } - if (!amount_msat_sub(&remaining, remaining, this_amount)) - abort(); - } - - /* Shrink the array we return */ - tal_resize(&flows, num_flows); - global_params = NULL; - return flows; -} - -#ifndef SUPERVERBOSE_ENABLED -#undef SUPERVERBOSE -#endif diff --git a/plugins/renepay/pay.c b/plugins/renepay/pay.c index 46b2f8788402..49b21de13111 100644 --- a/plugins/renepay/pay.c +++ b/plugins/renepay/pay.c @@ -15,23 +15,27 @@ #include #include #include -#include -#include +#include +#include +#include -/* Set in init */ -struct pay_plugin *pay_plugin; +// TODO(eduardo): maybe there are too many debug_err and plugin_err and +// plugin_log(...,LOG_BROKEN,...) that could be resolved with a command_fail -void paynote(struct payment *p, const char *fmt, ...) -{ - va_list ap; - const char *str; - - va_start(ap, fmt); - str = tal_vfmt(p->paynotes, fmt, ap); - va_end(ap); - tal_arr_expand(&p->paynotes, str); - plugin_log(pay_plugin->plugin, LOG_DBG, "%s", str); -} +#define INVALID_ID UINT64_MAX +#define MAX(a,b) ((a)>(b)? (a) : (b)) + +static struct pay_plugin the_pay_plugin; +struct pay_plugin * const pay_plugin = &the_pay_plugin; + +static void timer_kick(struct renepay * renepay); +static struct command_result *try_paying(struct command *cmd, + struct renepay * renepay, + bool first_time); + +// TODO(eduardo): maybe we don't need these +static void background_timer_kick(void*p UNUSED); +static void background_settimer(void); void amount_msat_accumulate_(struct amount_msat *dst, struct amount_msat src, @@ -40,7 +44,7 @@ void amount_msat_accumulate_(struct amount_msat *dst, { if (amount_msat_add(dst, *dst, src)) return; - plugin_err(pay_plugin->plugin, "Overflow adding %s (%s) into %s (%s)", + debug_err("Overflow adding %s (%s) into %s (%s)", srcname, type_to_string(tmpctx, struct amount_msat, &src), dstname, type_to_string(tmpctx, struct amount_msat, dst)); } @@ -52,26 +56,45 @@ void amount_msat_reduce_(struct amount_msat *dst, { if (amount_msat_sub(dst, *dst, src)) return; - plugin_err(pay_plugin->plugin, "Underflow subtracting %s (%s) from %s (%s)", + debug_err("Underflow subtracting %s (%s) from %s (%s)", srcname, type_to_string(tmpctx, struct amount_msat, &src), dstname, type_to_string(tmpctx, struct amount_msat, dst)); } + #if DEVELOPER static void memleak_mark(struct plugin *p, struct htable *memtable) { - memleak_scan_obj(memtable, pay_plugin); - memleak_scan_htable(memtable, &pay_plugin->chan_extra_map.raw); + /* TODO(eduardo): understand the purpose of memleak_scan_obj, why use it + * instead of tal_free? + * 1st problem: this is executed before the plugin can process the + * shutdown notification, + * 2nd problem: memleak_scan_obj does not propagate to children. + * For the moment let's just (incorrectly) do tal_free here + * */ + pay_plugin->ctx = tal_free(pay_plugin->ctx); + + // memleak_scan_obj(memtable, pay_plugin->ctx); + // memleak_scan_obj(memtable, pay_plugin->gossmap); + // memleak_scan_obj(memtable, pay_plugin->chan_extra_map); + // memleak_scan_htable(memtable, &pay_plugin->chan_extra_map->raw); } #endif +static void destroy_payflow(struct pay_flow *flow) +{ + remove_htlc_payflow(pay_plugin->chan_extra_map,flow); + payflow_map_del(pay_plugin->payflow_map, flow); +} + static const char *init(struct plugin *p, const char *buf UNUSED, const jsmntok_t *config UNUSED) { size_t num_channel_updates_rejected; - pay_plugin = tal(p, struct pay_plugin); + pay_plugin->ctx = notleak_with_children(tal(p,tal_t)); pay_plugin->plugin = p; + pay_plugin->rexmit_timer=NULL; rpc_scan(p, "getinfo", take(json_out_obj(NULL, NULL, NULL)), "{id:%}", JSON_SCAN(json_to_node_id, &pay_plugin->my_id)); @@ -80,14 +103,21 @@ static const char *init(struct plugin *p, take(json_out_obj(NULL, NULL, NULL)), "{max-locktime-blocks:%,experimental-offers:%}", JSON_SCAN(json_to_number, &pay_plugin->maxdelay_default), - JSON_SCAN(json_to_bool, &pay_plugin->exp_offers)); + JSON_SCAN(json_to_bool, &pay_plugin->exp_offers) + ); list_head_init(&pay_plugin->payments); - chan_extra_map_init(&pay_plugin->chan_extra_map); - - pay_plugin->gossmap = gossmap_load(NULL, + + pay_plugin->chan_extra_map = tal(pay_plugin->ctx,struct chan_extra_map); + chan_extra_map_init(pay_plugin->chan_extra_map); + + pay_plugin->payflow_map = tal(pay_plugin->ctx,struct payflow_map); + payflow_map_init(pay_plugin->payflow_map); + + pay_plugin->gossmap = gossmap_load(pay_plugin->ctx, GOSSIP_STORE_FILENAME, &num_channel_updates_rejected); + if (!pay_plugin->gossmap) plugin_err(p, "Could not load gossmap %s: %s", GOSSIP_STORE_FILENAME, strerror(errno)); @@ -95,311 +125,122 @@ static const char *init(struct plugin *p, plugin_log(p, LOG_DBG, "gossmap ignored %zu channel updates", num_channel_updates_rejected); - + + uncertainty_network_update(pay_plugin->gossmap, + pay_plugin->chan_extra_map); #if DEVELOPER plugin_set_memleak_handler(p, memleak_mark); #endif - + + background_settimer(); return NULL; } -/* We know something about this channel! Update it! */ -static void chan_update_capacity(struct payment *p, - struct short_channel_id scid, - int dir, - const struct amount_msat *min_capacity, - const struct amount_msat *max_capacity) -{ - struct chan_extra_half *h; - - /* This is assumed */ - if (min_capacity && max_capacity) - assert(amount_msat_greater_eq(*max_capacity, *min_capacity)); - - h = get_chan_extra_half_by_scid(&pay_plugin->chan_extra_map, scid, dir); - if (!h) - h = new_chan_extra_half(&pay_plugin->chan_extra_map, scid, dir, - *max_capacity); - - if (min_capacity && amount_msat_greater(*min_capacity, h->known_min)) - h->known_min = *min_capacity; - if (max_capacity && amount_msat_less(*max_capacity, h->known_max)) - h->known_max = *max_capacity; - - /* If we min > max, it means our previous assumptions are wrong - * (i.e. things changed, or a we assumed full capacity for a routehint - * which didn't have it!) */ - if (amount_msat_greater(h->known_min, h->known_max)) { - plugin_log(pay_plugin->plugin, LOG_BROKEN, - "Updated %s capacity %s, now %s-%s! Resetting.", - type_to_string(tmpctx, struct short_channel_id, &scid), - min_capacity ? "min" : "max", - type_to_string(tmpctx, struct amount_msat, &h->known_min), - type_to_string(tmpctx, struct amount_msat, &h->known_max)); - - /* OK, assume *old* information is wrong: we can't have - * just set both, since we assert() those are correct. */ - if (min_capacity) { - const struct gossmap_chan *c; - struct amount_sat cap; - - /* It might be a local channel; if we don't know better, - * we reset max to infinite */ - c = gossmap_find_chan(pay_plugin->gossmap, &scid); - if (!c - || !gossmap_chan_get_capacity(pay_plugin->gossmap, c, &cap) - || !amount_sat_to_msat(&h->known_max, cap)) - h->known_max = p->maxspend; - plugin_log(pay_plugin->plugin, LOG_UNUSUAL, - "... setting max to capacity (%s)", - type_to_string(tmpctx, struct amount_msat, - &h->known_max)); - } else { - h->known_min = AMOUNT_MSAT(0); - plugin_log(pay_plugin->plugin, LOG_UNUSUAL, - "... setting min to 0msat"); - } - } -} - -static void add_localchan(struct payment *p, - 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, - struct amount_msat maxspend) -{ - int dir = node_id_cmp(src, dst) < 0 ? 0 : 1; - /* FIXME: features? */ - gossmap_local_addchan(p->local_gossmods, src, dst, &scid, NULL); - - gossmap_local_updatechan(p->local_gossmods, - &scid, - /* We assume any HTLC is allowed */ - AMOUNT_MSAT(0), maxspend, - fee_base_msat, fee_proportional_millionths, - cltv_expiry_delta, - true, - dir); - /* We know (assume!) something about this channel: that it has at - * sufficient capacity. */ - chan_update_capacity(p, scid, dir, &p->maxspend, &p->maxspend); -} -/* Add routehints provided by bolt11 */ -static void add_routehints(struct payment *p, - struct route_info **routes, - const struct node_id *dest) +// /* TODO(eduardo): an example of an RPC call that is not bound to any command. */ +//static +//struct command_result* getinfo_done(struct command *cmd UNUSED, +// const char *buf, +// const jsmntok_t *result, +// void* pp UNUSED) +//{ +// struct node_id id; +// const jsmntok_t *id_tok = json_get_member(buf,result,"id"); +// json_to_node_id(buf,id_tok,&id); +// +// plugin_log(pay_plugin->plugin,LOG_DBG, +// "calling %s, nodeid = %s", +// __PRETTY_FUNCTION__, +// type_to_string(tmpctx,struct node_id,&id)); +// +// return command_still_pending(NULL); +//} + +static void background_settimer(void) { - for (size_t i = 0; i < tal_count(routes); i++) { - /* Each one, presumably, leads to the destination */ - const struct route_info *r = routes[i]; - const struct node_id *end = dest; - for (int j = tal_count(r)-1; j >= 0; j--) { - add_localchan(p, &r[j].pubkey, end, - r[j].cltv_expiry_delta, - r[j].short_channel_id, - r[j].fee_base_msat, - r[j].fee_proportional_millionths, - p->maxspend); - dest = &r[j].pubkey; - } - } + pay_plugin->rexmit_timer + = tal_free(pay_plugin->rexmit_timer); + pay_plugin->rexmit_timer + = plugin_timer( + pay_plugin->plugin, + time_from_msec(2000), + background_timer_kick, NULL); } -/* listpeers gives us the certainty on local channels' capacity. Of course, - * this is racy and transient, but better than nothing! */ -static bool update_capacities_from_listpeers(struct plugin *plugin, - struct payment *p, - const char *buf, - const jsmntok_t *toks) +static void background_timer_kick(void * p UNUSED) { - const jsmntok_t *peers, *peer; - size_t i; - - if (json_get_member(buf, toks, "error")) - goto malformed; - - peers = json_get_member(buf, toks, "peers"); - if (!peers) - goto malformed; - - json_for_each_arr(i, peer, peers) { - const jsmntok_t *channel, *channels; - size_t j; - bool connected; - - channels = json_get_member(buf, peer, "channels"); - if (!channels) - continue; - - if (!json_to_bool(buf, - json_get_member(buf, peer, "connected"), - &connected)) - goto malformed; - - json_for_each_arr(j, channel, channels) { - const jsmntok_t *spendabletok, *scidtok, *dirtok, *statetok; - struct short_channel_id scid; - int dir; - struct amount_msat spendable; - - scidtok = json_get_member(buf, channel, "short_channel_id"); - /* If channel is still opening, this won't be there */ - if (!scidtok) - continue; - - spendabletok = json_get_member(buf, channel, "spendable_msat"); - dirtok = json_get_member(buf, channel, "direction"); - /* FIXME: Example max_accepted_htlcs and htlcs */ - statetok = json_get_member(buf, channel, "state"); - if (spendabletok == NULL - || dirtok == NULL - || statetok == NULL) - goto malformed; - - if (!json_to_short_channel_id(buf, scidtok, &scid)) - goto malformed; - if (!json_to_int(buf, dirtok, &dir)) - goto malformed; - if (!json_to_msat(buf, spendabletok, &spendable)) - goto malformed; - - /* Don't report opening/closing channels */ - if (!json_tok_streq(buf, statetok, "CHANNELD_NORMAL")) { - tal_arr_expand(&p->disabled, scid); - continue; - } - - if (!connected) { - paynote(p, "local channel %s disabled:" - " peer disconnected", - type_to_string(tmpctx, - struct short_channel_id, - &scid)); - tal_arr_expand(&p->disabled, scid); - continue; - } - - /* We know min and max capacity exactly now! */ - chan_update_capacity(p, scid, dir, &spendable, &spendable); - } - } - return true; - -malformed: - plugin_log(plugin, LOG_BROKEN, - "listpeers malformed: %.*s", - json_tok_full_len(toks), - json_tok_full(buf, toks)); - return false; + // plugin_log(pay_plugin->plugin,LOG_DBG,"calling %s",__PRETTY_FUNCTION__); + background_settimer(); + + // /* TODO(eduardo): an example of an RPC call that is not bound to any command. */ + // struct out_req * req = jsonrpc_request_start(pay_plugin->plugin, + // NULL, + // "getinfo", + // getinfo_done, + // getinfo_done, + // NULL); + // send_outreq(pay_plugin->plugin, req); } -/* How much did does this flow deliver to dest? */ -static struct amount_msat flow_delivered(const struct pay_flow *flow) +static void renepay_settimer(struct renepay * renepay) { - return flow->amounts[tal_count(flow->amounts)-1]; -} - -/* Mutual recursion */ -static struct command_result *try_paying(struct command *cmd, - struct payment *p, - bool first_time); - -static struct command_result *flow_failed(struct command *cmd, - const struct pay_flow *flow) -{ - struct payment *p = flow->payment; - - amount_msat_reduce(&p->total_delivering, flow_delivered(flow)); - amount_msat_reduce(&p->total_sent, flow->amounts[0]); - - /* If nothing outstanding, don't wait for timer! */ - if (amount_msat_eq(p->total_sent, AMOUNT_MSAT(0))) { - p->rexmit_timer = tal_free(p->rexmit_timer); - return try_paying(cmd, p, false); - } - - /* Still waiting for timer... */ - return command_still_pending(cmd); + renepay->rexmit_timer = tal_free(renepay->rexmit_timer); + renepay->rexmit_timer = plugin_timer( + pay_plugin->plugin, + time_from_msec(TIMER_COLLECT_FAILURES_MSEC), + timer_kick, renepay); } /* Happens when timer goes off, but also works to arm timer if nothing to do */ -static void timer_kick(struct payment *p) -{ - p->rexmit_timer = tal_free(p->rexmit_timer); - - /* Nothing has come back? Re-arm timer. */ - if (amount_msat_eq(p->total_delivering, p->amount)) { - p->rexmit_timer = plugin_timer(p->cmd->plugin, - time_from_msec(250), - timer_kick, p); - return; - } - - try_paying(p->cmd, p, false); -} - -static struct command_result *waitsendpay_succeeded(struct command *cmd, - const char *buf, - const jsmntok_t *result, - struct pay_flow *flow) -{ - struct payment *p = flow->payment; - struct json_stream *response = jsonrpc_stream_success(cmd); - const jsmntok_t *preimagetok; - - preimagetok = json_get_member(buf, result, "payment_preimage"); - if (!preimagetok) - plugin_err(cmd->plugin, - "Strange return from waitsendpay: %.*s", - json_tok_full_len(result), - json_tok_full(buf, result)); - - /* Any one succeeding is success. */ - json_add_tok(response, "payment_preimage", preimagetok, buf); - json_add_sha256(response, "payment_hash", &p->payment_hash); - json_add_timeabs(response, "created_at", p->start_time); - json_add_u32(response, "parts", p->next_partid); - json_add_amount_msat_only(response, "amount_msat", - p->total_delivering); - json_add_amount_msat_only(response, "amount_sent_msat", - p->total_sent); - json_add_string(response, "status", "complete"); - json_add_node_id(response, "destination", &p->dest); - return command_finished(cmd, response); -} - -static const char *flow_path_to_str(const tal_t *ctx, const struct pay_flow *flow) +static void timer_kick(struct renepay * renepay) { - char *s = tal_strdup(ctx, ""); - for (size_t i = 0; i < tal_count(flow->path_scids); i++) { - tal_append_fmt(&s, "-%s->", - type_to_string(tmpctx, struct short_channel_id, - &flow->path_scids[i])); + struct payment * const p = renepay->payment; + plugin_log(pay_plugin->plugin,LOG_DBG,"calling %s",__PRETTY_FUNCTION__); + + switch(p->status) + { + /* Some flows succeeded, we finish the payment. */ + case PAYMENT_SUCCESS: + renepay_success(renepay); + break; + + /* Some flows failed, we retry. */ + case PAYMENT_FAIL: + payment_assert_delivering_incomplete(p); + try_paying(renepay->cmd,renepay,false); + break; + + /* Nothing has returned yet, we have to wait. */ + case PAYMENT_PENDING: + payment_assert_delivering_all(p); + renepay_settimer(renepay); + break; } - return s; } /* Sometimes we don't know exactly who to blame... */ -static struct command_result *handle_unhandleable_error(struct payment *p, - const struct pay_flow *flow, +static struct command_result *handle_unhandleable_error(struct renepay * renepay, + struct pay_flow *flow, const char *what) { + struct payment * const p = renepay->payment; + plugin_log(pay_plugin->plugin,LOG_DBG,"calling %s",__PRETTY_FUNCTION__); size_t n = tal_count(flow); /* We got a mangled reply. We don't know who to penalize! */ - paynote(p, "%s on route %s", what, flow_path_to_str(tmpctx, flow)); + debug_paynote(p, "%s on route %s", what, flow_path_to_str(tmpctx, flow)); + + // TODO(eduardo): does LOG_BROKEN finish the plugin execution? plugin_log(pay_plugin->plugin, LOG_BROKEN, "%s on route %s", what, flow_path_to_str(tmpctx, flow)); if (n == 1) - return command_fail(p->cmd, PAY_UNPARSEABLE_ONION, + { + payflow_fail(flow); + return renepay_fail(renepay, PAY_UNPARSEABLE_ONION, "Got %s from the destination", what); - + } /* FIXME: check chan_extra_map, since we might have succeeded though * this node before? */ @@ -411,8 +252,8 @@ static struct command_result *handle_unhandleable_error(struct payment *p, /* Assume it's not the destination */ n = pseudorand(n-1); - tal_arr_expand(&p->disabled, flow->path_scids[n]); - paynote(p, "... eliminated %s", + tal_arr_expand(&renepay->disabled, flow->path_scids[n]); + debug_paynote(p, "... eliminated %s", type_to_string(tmpctx, struct short_channel_id, &flow->path_scids[n])); return NULL; @@ -422,7 +263,7 @@ static struct command_result *handle_unhandleable_error(struct payment *p, * gossipd to receive the channel_update we got from the error. */ struct addgossip { struct short_channel_id scid; - const struct pay_flow *flow; + struct pay_flow *flow; }; static struct command_result *addgossip_done(struct command *cmd, @@ -430,12 +271,15 @@ static struct command_result *addgossip_done(struct command *cmd, const jsmntok_t *err, struct addgossip *adg) { - struct payment *p = adg->flow->payment; + plugin_log(pay_plugin->plugin,LOG_DBG,"calling %s",__PRETTY_FUNCTION__); + struct renepay * renepay = adg->flow->payment->renepay; /* Release this: if it's the last flow we'll retry immediately */ + + payflow_fail(adg->flow); tal_free(adg); - timer_kick(p); - + renepay_settimer(renepay); + return command_still_pending(cmd); } @@ -445,33 +289,38 @@ static struct command_result *addgossip_failure(struct command *cmd, struct addgossip *adg) { - struct payment *p = adg->flow->payment; + plugin_log(pay_plugin->plugin,LOG_DBG,"calling %s",__PRETTY_FUNCTION__); + struct payment * p = adg->flow->payment; + struct renepay * renepay = p->renepay; - paynote(p, "addgossip failed, removing channel %s (%.*s)", + debug_paynote(p, "addgossip failed, removing channel %s (%.*s)", type_to_string(tmpctx, struct short_channel_id, &adg->scid), err->end - err->start, buf + err->start); - tal_arr_expand(&p->disabled, adg->scid); + tal_arr_expand(&renepay->disabled, adg->scid); return addgossip_done(cmd, buf, err, adg); } static struct command_result *submit_update(struct command *cmd, - const struct pay_flow *flow, + struct pay_flow *flow, const u8 *update, struct short_channel_id errscid) { - struct payment *p = flow->payment; + plugin_log(pay_plugin->plugin,LOG_DBG,"calling %s",__PRETTY_FUNCTION__); + struct payment * p = flow->payment; + struct renepay * renepay = p->renepay; struct out_req *req; struct addgossip *adg = tal(cmd, struct addgossip); /* We need to stash scid in case this fails, and we need to hold flow so * we don't get a rexmit before this is complete. */ adg->scid = errscid; - adg->flow = tal_steal(adg, flow); + adg->flow = flow; /* Disable re-xmit until this returns */ - p->rexmit_timer = tal_free(p->rexmit_timer); + renepay->rexmit_timer + = tal_free(renepay->rexmit_timer); - paynote(p, "... extracted channel_update, telling gossipd"); + debug_paynote(p, "... extracted channel_update, telling gossipd"); plugin_log(pay_plugin->plugin, LOG_DBG, "(update = %s)", tal_hex(tmpctx, update)); req = jsonrpc_request_start(pay_plugin->plugin, NULL, "addgossip", @@ -503,6 +352,7 @@ static u8 *patch_channel_update(const tal_t *ctx, u8 *channel_update TAKES) } } + /* Return NULL if the wrapped onion error message has no channel_update field, * or return the embedded channel_update message otherwise. */ static u8 *channel_update_from_onion_error(const tal_t *ctx, @@ -539,192 +389,14 @@ static u8 *channel_update_from_onion_error(const tal_t *ctx, return patch_channel_update(ctx, take(channel_update)); } -static struct command_result *waitsendpay_failed(struct command *cmd, - const char *buf, - const jsmntok_t *err, - struct pay_flow *flow) -{ - struct payment *p = flow->payment; - u64 errcode; - struct command_result *ret; - const jsmntok_t *datatok, *msgtok, *errchantok, *erridxtok, *failcodetok, *rawoniontok; - u32 onionerr, erridx; - struct short_channel_id errscid; - const u8 *update; - - if (!json_to_u64(buf, json_get_member(buf, err, "code"), &errcode)) - plugin_err(cmd->plugin, "Bad errcode from waitsendpay: %.*s", - json_tok_full_len(err), json_tok_full(buf, err)); - - switch (errcode) { - case PAY_UNPARSEABLE_ONION: - ret = handle_unhandleable_error(p, flow, "unparsable onion reply"); - if (ret) - return ret; - goto done; - case PAY_DESTINATION_PERM_FAIL: - return command_fail(cmd, PAY_DESTINATION_PERM_FAIL, - "Got an final failure from destination"); - case PAY_TRY_OTHER_ROUTE: - break; - - default: - plugin_err(cmd->plugin, - "Unexpected errcode from waitsendpay: %.*s", - json_tok_full_len(err), json_tok_full(buf, err)); - } - - datatok = json_get_member(buf, err, "data"); - - /* OK, we expect an onion error reply. */ - erridxtok = json_get_member(buf, datatok, "erring_index"); - if (!erridxtok) { - ret = handle_unhandleable_error(p, flow, "Missing erring_index"); - if (ret) - return ret; - goto done; - } - json_to_u32(buf, erridxtok, &erridx); - errchantok = json_get_member(buf, datatok, "erring_channel"); - json_to_short_channel_id(buf, errchantok, &errscid); - if (!short_channel_id_eq(&errscid, &flow->path_scids[erridx])) - plugin_err(pay_plugin->plugin, "Erring channel %u/%zu was %s not %s (path %s)", - erridx, tal_count(flow->path_scids), - type_to_string(tmpctx, struct short_channel_id, &errscid), - type_to_string(tmpctx, struct short_channel_id, &flow->path_scids[erridx]), - flow_path_to_str(tmpctx, flow)); - - failcodetok = json_get_member(buf, datatok, "failcode"); - json_to_u32(buf, failcodetok, &onionerr); - - msgtok = json_get_member(buf, datatok, "message"); - rawoniontok = json_get_member(buf, datatok, "raw_message"); - - paynote(p, "onion error %s from node #%u %s: %.*s", - onion_wire_name(onionerr), - erridx, - type_to_string(tmpctx, struct short_channel_id, &errscid), - msgtok->end - msgtok->start, buf + msgtok->start); - - /* All these parts succeeded, so we know something about min - * capacity! */ - for (size_t i = 0; i < erridx; i++) - chan_update_capacity(p, flow->path_scids[i], flow->path_dirs[i], &flow->amounts[i], - NULL); - - switch ((enum onion_wire)onionerr) { - /* These definitely mean eliminate channel */ - case WIRE_PERMANENT_CHANNEL_FAILURE: - case WIRE_REQUIRED_CHANNEL_FEATURE_MISSING: - /* FIXME: lnd returns this for disconnected peer, so don't disable perm! */ - case WIRE_UNKNOWN_NEXT_PEER: - case WIRE_CHANNEL_DISABLED: - /* These mean node is weird, but we eliminate channel here too */ - case WIRE_INVALID_REALM: - case WIRE_TEMPORARY_NODE_FAILURE: - case WIRE_PERMANENT_NODE_FAILURE: - case WIRE_REQUIRED_NODE_FEATURE_MISSING: - /* These shouldn't happen, but eliminate channel */ - case WIRE_INVALID_ONION_VERSION: - case WIRE_INVALID_ONION_HMAC: - case WIRE_INVALID_ONION_KEY: - case WIRE_INVALID_ONION_PAYLOAD: - case WIRE_INVALID_ONION_BLINDING: - case WIRE_EXPIRY_TOO_FAR: - paynote(p, "... so we're removing scid"); - tal_arr_expand(&p->disabled, errscid); - goto done; - - /* These can be fixed (maybe) by applying the included channel_update */ - case WIRE_AMOUNT_BELOW_MINIMUM: - case WIRE_FEE_INSUFFICIENT: - case WIRE_INCORRECT_CLTV_EXPIRY: - case WIRE_EXPIRY_TOO_SOON: - /* FIXME: Check scid! */ - update = channel_update_from_onion_error(tmpctx, buf, rawoniontok); - if (update) - return submit_update(cmd, flow, update, errscid); - - paynote(p, "... missing an update, so we're removing scid"); - tal_arr_expand(&p->disabled, errscid); - goto done; - - /* Insufficient funds! */ - case WIRE_TEMPORARY_CHANNEL_FAILURE: { - /* OK new max is amount - 1 */ - struct amount_msat max_possible; - if (!amount_msat_sub(&max_possible, - flow->amounts[erridx], - AMOUNT_MSAT(1))) - max_possible = AMOUNT_MSAT(0); - paynote(p, "... assuming that max capacity is %s", - type_to_string(tmpctx, struct amount_msat, - &max_possible)); - chan_update_capacity(p, errscid, flow->path_dirs[erridx], NULL, &max_possible); - goto done; - } - - /* We ignore this, assuming we want to keep going. */ - /* FIXME: We should probably pause until all parts returned, or at least - * extend rexmit timer! */ - case WIRE_MPP_TIMEOUT: - paynote(p, "... will continue"); - goto done; - - /* These are from the final distination: fail */ - case WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS: - case WIRE_FINAL_INCORRECT_CLTV_EXPIRY: - case WIRE_FINAL_INCORRECT_HTLC_AMOUNT: - paynote(p, "... fatal"); - return command_fail(cmd, PAY_DESTINATION_PERM_FAIL, - "Destination said %s: %.*s", - onion_wire_name(onionerr), - msgtok->end - msgtok->start, - buf + msgtok->start); - } - /* Unknown response? */ - if (erridx == tal_count(flow->path_nodes)) { - paynote(p, "... fatal"); - return command_fail(cmd, PAY_DESTINATION_PERM_FAIL, - "Destination gave unknown error code %u: %.*s", - onionerr, - msgtok->end - msgtok->start, - buf + msgtok->start); - } - paynote(p, "... eliminating and continuing"); - plugin_log(pay_plugin->plugin, LOG_BROKEN, - "Node %s (%u/%zu) gave unknown error code %u", - type_to_string(tmpctx, struct node_id, - &flow->path_nodes[erridx]), - erridx, tal_count(flow->path_nodes), - onionerr); - tal_arr_expand(&p->disabled, errscid); - -done: - return flow_failed(cmd, flow); -} - /* Once we've sent it, we immediate wait for reply. */ static struct command_result *flow_sent(struct command *cmd, const char *buf, const jsmntok_t *result, struct pay_flow *flow) { - struct payment *p = flow->payment; - struct out_req *req; - - req = jsonrpc_request_start(cmd->plugin, cmd, "waitsendpay", - waitsendpay_succeeded, - waitsendpay_failed, - cast_const(struct pay_flow *, flow)); - json_add_sha256(req->js, "payment_hash", &p->payment_hash); - json_add_u64(req->js, "groupid", p->groupid); - json_add_u64(req->js, "partid", flow->partid); - /* FIXME: We don't set timeout... */ - - /* Now flow is owned by this request */ - tal_steal(req, flow); - return send_outreq(cmd->plugin, req); + plugin_log(pay_plugin->plugin,LOG_DBG,"calling %s",__PRETTY_FUNCTION__); + return command_still_pending(cmd); } /* sendpay really only fails immediately in two ways: @@ -736,7 +408,16 @@ static struct command_result *flow_sendpay_failed(struct command *cmd, const jsmntok_t *err, struct pay_flow *flow) { + plugin_log(pay_plugin->plugin,LOG_DBG,"calling %s",__PRETTY_FUNCTION__); + struct payment *p = flow->payment; + debug_assert(p); + struct renepay * renepay = p->renepay; + debug_assert(renepay); + + /* This is a fail. */ + payment_fail(p); + u64 errcode; const jsmntok_t *msg = json_get_member(buf, err, "message"); @@ -748,19 +429,29 @@ static struct command_result *flow_sendpay_failed(struct command *cmd, plugin_err(cmd->plugin, "Strange error from sendpay: %.*s", json_tok_full_len(err), json_tok_full(buf, err)); - paynote(p, + debug_paynote(p, "sendpay didn't like first hop, eliminated: %.*s", msg->end - msg->start, buf + msg->start); - tal_arr_expand(&p->disabled, flow->path_scids[0]); - return flow_failed(cmd, flow); + + /* There is no new knowledge from this kind of failure. + * We just disable this scid. */ + tal_arr_expand(&renepay->disabled, flow->path_scids[0]); + + payflow_fail(flow); + return command_still_pending(cmd); } + static struct command_result * sendpay_flows(struct command *cmd, - struct payment *p, + struct renepay * renepay, struct pay_flow **flows STEALS) { - paynote(p, "Sending out batch of %zu payments", tal_count(flows)); + struct payment * const p = renepay->payment; + + plugin_log(pay_plugin->plugin,LOG_DBG,"calling %s",__PRETTY_FUNCTION__); + debug_paynote(p, "Sending out batch of %zu payments", tal_count(flows)); + for (size_t i = 0; i < tal_count(flows); i++) { struct out_req *req; req = jsonrpc_request_start(cmd->plugin, cmd, "sendpay", @@ -776,14 +467,22 @@ sendpay_flows(struct command *cmd, &flows[i]->path_scids[j]); json_add_amount_msat_only(req->js, "amount_msat", flows[i]->amounts[j]); + json_add_num(req->js, "direction", + flows[i]->path_dirs[j]); json_add_u32(req->js, "delay", flows[i]->cltv_delays[j]); + json_add_string(req->js,"style","tlv"); + json_object_end(req->js); } json_array_end(req->js); json_add_sha256(req->js, "payment_hash", &p->payment_hash); + json_add_secret(req->js, "payment_secret", p->payment_secret); + json_add_amount_msat_only(req->js, "amount_msat", p->amount); - json_add_u64(req->js, "partid", flows[i]->partid); + + json_add_u64(req->js, "partid", flows[i]->key.partid); + json_add_u64(req->js, "groupid", p->groupid); if (p->payment_metadata) json_add_hex_talarr(req->js, "payment_metadata", @@ -795,76 +494,168 @@ sendpay_flows(struct command *cmd, json_add_string(req->js, "bolt11", p->invstr); if (p->description) json_add_string(req->js, "description", p->description); - - /* Flow now owned by request */ - tal_steal(req, flows[i]); - send_outreq(cmd->plugin, req); + amount_msat_accumulate(&p->total_sent, flows[i]->amounts[0]); amount_msat_accumulate(&p->total_delivering, - flow_delivered(flows[i])); + payflow_delivered(flows[i])); + + /* Flow now owned by all_flows instead of req., in this way we + * can control the destruction occurs before we remove temporary + * channels from chan_extra_map. */ + tal_steal(pay_plugin->ctx,flows[i]); + + /* Let's keep record of this flow. */ + payflow_map_add(pay_plugin->payflow_map,flows[i]); + + /* record these HTLC along the flow path */ + commit_htlc_payflow(pay_plugin->chan_extra_map,flows[i]); + + /* Remove the HTLC from the chan_extra_map after finish. */ + tal_add_destructor(flows[i], destroy_payflow); + + send_outreq(cmd->plugin, req); } - + + /* Safety check. */ + payment_assert_delivering_all(p); + + tal_free(flows); + /* Get ready to process replies */ - timer_kick(p); - + renepay_settimer(renepay); + return command_still_pending(cmd); } static struct command_result *try_paying(struct command *cmd, - struct payment *p, + struct renepay *renepay, bool first_time) { + struct payment * const p = renepay->payment; + plugin_log(pay_plugin->plugin,LOG_DBG,"calling %s",__PRETTY_FUNCTION__); + + // TODO(eduardo): does it make sense to have this limit on attempts? + /* I am classifying the flows in attempt cycles. */ + renepay_new_attempt(renepay); + /* We try only MAX_NUM_ATTEMPTS, then we give up. */ + if ( renepay_attempt_count(renepay) > MAX_NUM_ATTEMPTS) + { + return renepay_fail(renepay, PAY_STOPPED_RETRYING, + "Reached maximum number of attempts (%d)", + MAX_NUM_ATTEMPTS); + } + struct amount_msat feebudget, fees_spent, remaining; - struct pay_flow **pay_flows; if (time_after(time_now(), p->stop_time)) - return command_fail(cmd, PAY_STOPPED_RETRYING, "Timed out"); - + return renepay_fail(renepay, PAY_STOPPED_RETRYING, "Timed out"); + /* Total feebudget */ if (!amount_msat_sub(&feebudget, p->maxspend, p->amount)) - abort(); + { + plugin_err(pay_plugin->plugin, + "%s (line %d) could not substract maxspend=%s and amount=%s.", + __PRETTY_FUNCTION__, + __LINE__, + type_to_string(tmpctx, struct amount_msat, &p->maxspend), + type_to_string(tmpctx, struct amount_msat, &p->amount)); + } /* Fees spent so far */ - if (amount_msat_sub(&fees_spent, p->total_sent, p->total_delivering)) - abort(); + if (!amount_msat_sub(&fees_spent, p->total_sent, p->total_delivering)) + { + plugin_err(pay_plugin->plugin, + "%s (line %d) could not substract total_sent=%s and total_delivering=%s.", + __PRETTY_FUNCTION__, + __LINE__, + type_to_string(tmpctx, struct amount_msat, &p->total_sent), + type_to_string(tmpctx, struct amount_msat, &p->total_delivering)); + } /* Remaining fee budget. */ if (!amount_msat_sub(&feebudget, feebudget, fees_spent)) - abort(); + { + plugin_err(pay_plugin->plugin, + "%s (line %d) could not substract feebudget=%s and fees_spent=%s.", + __PRETTY_FUNCTION__, + __LINE__, + type_to_string(tmpctx, struct amount_msat, &feebudget), + type_to_string(tmpctx, struct amount_msat, &fees_spent)); + } /* How much are we still trying to send? */ if (!amount_msat_sub(&remaining, p->amount, p->total_delivering)) - abort(); + { + plugin_err(pay_plugin->plugin, + "%s (line %d) could not substract amount=%s and total_delivering=%s.", + __PRETTY_FUNCTION__, + __LINE__, + type_to_string(tmpctx, struct amount_msat, &p->amount), + type_to_string(tmpctx, struct amount_msat, &p->total_delivering)); + } + // plugin_log(pay_plugin->plugin,LOG_DBG,fmt_chan_extra_map(tmpctx,pay_plugin->chan_extra_map)); + + char const * err_msg; + /* We let this return an unlikely path, as it's better to try once * than simply refuse. Plus, models are not truth! */ - pay_flows = get_payflows(p, remaining, feebudget, first_time, - amount_msat_eq(p->total_delivering, AMOUNT_MSAT(0))); + struct pay_flow **pay_flows = get_payflows( + renepay, + remaining, feebudget, + + /* would you accept unlikely + * payments? */ + first_time, + + /* is entire payment? */ + amount_msat_eq(p->total_delivering, AMOUNT_MSAT(0)), + + &err_msg); + + plugin_log(pay_plugin->plugin,LOG_DBG,fmt_payflows(tmpctx,pay_flows)); + + /* MCF cannot find a feasible route, we stop. */ + // TODO(eduardo): alternatively we can fallback to `pay`. if (!pay_flows) - return command_fail(cmd, PAY_ROUTE_NOT_FOUND, - "Failed to find a route for %s with budget %s", - type_to_string(tmpctx, struct amount_msat, - &remaining), - type_to_string(tmpctx, struct amount_msat, - &feebudget)); - + { + return renepay_fail(renepay, PAY_ROUTE_NOT_FOUND, + "Failed to find a route, %s", + err_msg); + } /* Now begin making payments */ - return sendpay_flows(cmd, p, pay_flows); + + return sendpay_flows(cmd, renepay, pay_flows); } -static struct command_result * -listpeers_done(struct command *cmd, const char *buf, - const jsmntok_t *result, struct payment *p) +static struct command_result *listpeerchannels_done( + struct command *cmd, + const char *buf, + const jsmntok_t *result, + struct renepay *renepay) { - if (!update_capacities_from_listpeers(cmd->plugin, p, buf, result)) - return command_fail(cmd, LIGHTNINGD, - "listpays malformed: %.*s", + plugin_log(pay_plugin->plugin,LOG_DBG,"calling %s",__PRETTY_FUNCTION__); + if (!uncertainty_network_update_from_listpeerchannels( + pay_plugin->chan_extra_map, + pay_plugin->my_id, + renepay, + buf, + result)) + return renepay_fail(renepay,LIGHTNINGD, + "listpeerchannels malformed: %.*s", json_tok_full_len(result), json_tok_full(buf, result)); - - return try_paying(cmd, p, true); + // So we have all localmods data, now we apply it. Only once per + // payment. + // TODO(eduardo): check that there won't be a prob. cost associated with + // any gossmap local chan. The same way there aren't fees to pay for my + // local channels. + gossmap_apply_localmods(pay_plugin->gossmap,renepay->local_gossmods); + renepay->localmods_applied=true; + return try_paying(cmd, renepay, true); } + static void destroy_payment(struct payment *p) { list_del_from(&pay_plugin->payments, &p->list); @@ -895,76 +686,345 @@ static struct command_result *json_paystatus(struct command *cmd, json_add_string(ret, "label", p->label); if (p->invstr) - json_add_invstring(ret, p->invstr); + json_add_invstring(ret,p->invstr); + json_add_amount_msat_only(ret, "amount_msat", p->amount); - - json_add_node_id(ret, "destination", &p->dest); + json_add_sha256(ret, "payment_hash", &p->payment_hash); + json_add_node_id(ret, "destination", &p->destination); + + if (p->description) + json_add_string(ret, "description", p->description); + + json_add_timeabs(ret,"created_at",p->start_time); + json_add_u64(ret,"groupid",p->groupid); + + switch(p->status) + { + case PAYMENT_SUCCESS: + json_add_string(ret,"status","complete"); + debug_assert(p->preimage); + json_add_preimage(ret,"payment_preimage",p->preimage); + json_add_amount_msat_only(ret, "amount_sent_msat", p->total_sent); + + break; + case PAYMENT_FAIL: + json_add_string(ret,"status","failed"); + + break; + default: + json_add_string(ret,"status","pending"); + } + json_array_start(ret, "notes"); for (size_t i = 0; i < tal_count(p->paynotes); i++) json_add_string(ret, NULL, p->paynotes[i]); json_array_end(ret); json_object_end(ret); + + // TODO(eduardo): maybe we should add also: + // - payment_secret? + // - payment_metadata? + // - number of parts? } json_array_end(ret); return command_finished(cmd, ret); } +/* Taken from ./plugins/pay.c + * + * We are interested in any prior attempts to pay this payment_hash / + * invoice so we can set the `groupid` correctly and ensure we don't + * already have a pending payment running. We also collect the summary + * about an eventual previous complete payment so we can return that + * as a no-op. */ +static struct command_result * +payment_listsendpays_previous( + struct command *cmd, + const char *buf, + const jsmntok_t *result, + struct renepay * renepay) +{ + debug_info("calling %s",__PRETTY_FUNCTION__); + struct payment * p = renepay->payment; + + size_t i; + const jsmntok_t *t, *arr, *err; + + /* Do we have pending sendpays for the previous attempt? */ + bool pending = false; + /* Group ID of the first pending payment, this will be the one + * who's result gets replayed if we end up suspending. */ + u64 first_pending_group_id = INVALID_ID; + u64 last_pending_group_id = INVALID_ID; + u64 last_pending_partid=0; + struct amount_msat pending_sent = AMOUNT_MSAT(0), + pending_msat = AMOUNT_MSAT(0); + + /* Did a prior attempt succeed? */ + bool completed = false; + /* Metadata for a complete payment, if one exists. */ + u32 complete_parts = 0; + struct preimage complete_preimage; + struct amount_msat complete_sent = AMOUNT_MSAT(0), + complete_msat = AMOUNT_MSAT(0); + u32 complete_created_at; + + u64 last_group=INVALID_ID; + + err = json_get_member(buf, result, "error"); + if (err) + return command_fail( + cmd, LIGHTNINGD, + "Error retrieving previous pay attempts: %s", + json_strdup(tmpctx, buf, err)); + + arr = json_get_member(buf, result, "payments"); + if (!arr || arr->type != JSMN_ARRAY) + return command_fail( + cmd, LIGHTNINGD, + "Unexpected non-array result from listsendpays"); + + /* We iterate through all prior sendpays, looking for the + * latest group and remembering what its state is. */ + json_for_each_arr(i, t, arr) + { + u64 partid, groupid; + struct amount_msat this_msat, this_sent; + + const jsmntok_t *status; + + // TODO(eduardo): assuming amount_msat is always known. + json_scan(tmpctx,buf,t, + "{partid:%" + ",groupid:%" + ",amount_msat:%" + ",amount_sent_msat:%}", + JSON_SCAN(json_to_u64,&partid), + JSON_SCAN(json_to_u64,&groupid), + JSON_SCAN(json_to_msat,&this_msat), + JSON_SCAN(json_to_msat,&this_sent)); + + /* status could be completed, pending or failed */ + + + status = json_get_member(buf, t, "status"); + + if(json_tok_streq(buf,status,"failed")) + continue; + + if(json_tok_streq(buf,status,"complete")) + { + /* Now we know the payment completed. */ + completed = true; + if(!amount_msat_add(&complete_msat,complete_msat,this_msat)) + debug_err("%s (line %d) msat overflow.", + __PRETTY_FUNCTION__,__LINE__); + if(!amount_msat_add(&complete_sent,complete_sent,this_sent)) + debug_err("%s (line %d) msat overflow.", + __PRETTY_FUNCTION__,__LINE__); + json_scan(tmpctx, buf, t, + "{created_at:%" + ",payment_preimage:%}", + JSON_SCAN(json_to_u32, &complete_created_at), + JSON_SCAN(json_to_preimage, &complete_preimage)); + complete_parts ++; + } + + if(json_tok_streq(buf,status,"pending")) + { + pending = true; // there are parts pending + + if(first_pending_group_id==INVALID_ID || + last_pending_group_id==INVALID_ID) + first_pending_group_id = last_pending_group_id = groupid; + + if(groupid > last_pending_group_id) + { + last_pending_group_id = groupid; + last_pending_partid = partid; + pending_msat = AMOUNT_MSAT(0); + pending_sent = AMOUNT_MSAT(0); + } + if(groupid < first_pending_group_id) + { + first_pending_group_id = groupid; + } + if(groupid == last_pending_group_id) + { + amount_msat_accumulate(&pending_sent,this_sent); + amount_msat_accumulate(&pending_msat,this_msat); + + last_pending_partid = MAX(last_pending_partid,partid); + plugin_log(pay_plugin->plugin,LOG_DBG, + "pending deliver increased by %s", + type_to_string(tmpctx,struct amount_msat,&this_msat)); + } + + } + } + + if (completed) { + struct json_stream *ret = jsonrpc_stream_success(cmd); + json_add_preimage(ret, "payment_preimage", &complete_preimage); + json_add_string(ret, "status", "complete"); + json_add_amount_msat_compat(ret, complete_msat, "msatoshi", + "amount_msat"); + json_add_amount_msat_compat(ret, complete_sent, "msatoshi_sent", + "amount_sent_msat"); + json_add_node_id(ret, "destination", &p->destination); + json_add_sha256(ret, "payment_hash", &p->payment_hash); + json_add_u32(ret, "created_at", complete_created_at); + json_add_num(ret, "parts", complete_parts); + + /* This payment was already completed, we don't keep record of + * it twice. */ + renepay->payment = tal_free(renepay->payment); + + return command_finished(cmd, ret); + } else if (pending) { + p->groupid = last_pending_group_id; + renepay->next_partid = last_pending_partid+1; + + p->total_sent = pending_sent; + p->total_delivering = pending_msat; + + plugin_log(pay_plugin->plugin,LOG_DBG, + "There are pending sendpays to this invoice. " + "groupid = %ld, " + "delivering = %s, " + "last_partid = %ld", + last_pending_group_id, + type_to_string(tmpctx,struct amount_msat,&p->total_delivering), + last_pending_partid); + + if( first_pending_group_id != last_pending_group_id) + { + /* At least two pending groups for the same invoice, + * this is weird, we better stop. */ + renepay->payment = tal_free(renepay->payment); + return renepay_fail(renepay, PAY_IN_PROGRESS, + "Payment is pending by some other request."); + } + if(amount_msat_greater_eq(p->total_delivering,p->amount)) + { + /* Pending payment already pays the full amount, we + * better stop. */ + renepay->payment = tal_free(renepay->payment); + return renepay_fail(renepay, PAY_IN_PROGRESS, + "Payment is pending with full amount already commited"); + } + }else + { + p->groupid = (last_group==INVALID_ID ? 1 : (last_group+1)) ; + renepay->next_partid=1; + } + + + struct out_req *req; + /* Get local capacities... */ + req = jsonrpc_request_start(cmd->plugin, cmd, "listpeerchannels", + listpeerchannels_done, + listpeerchannels_done, renepay); + return send_outreq(cmd->plugin, req); +} + static struct command_result *json_pay(struct command *cmd, const char *buf, const jsmntok_t *params) { - struct payment *p; - unsigned int *retryfor; + const char *invstr; + const char *label; + const char *description; + struct sha256 * local_offer_id; + u64 invexpiry; + struct amount_msat *msat, *invmsat; + struct amount_msat *maxfee; u64 *riskfactor_millionths; - struct amount_msat *invmsat, *msat, *maxfee; - u64 invexpiry; u32 *maxdelay; - struct out_req *req; + u64 *base_fee_penalty; + u64 *prob_cost_factor; + u64 *min_prob_success_millionths; + u32 *retryfor; + #if DEVELOPER bool *use_shadow; #endif - - p = tal(cmd, struct payment); - p->cmd = cmd; - p->paynotes = tal_arr(p, const char *, 0); - p->disabled = tal_arr(p, struct short_channel_id, 0); - p->groupid = pseudorand_u64(); - p->next_partid = 0; - p->total_sent = AMOUNT_MSAT(0); - p->total_delivering = AMOUNT_MSAT(0); - p->rexmit_timer = NULL; - + if (!param(cmd, buf, params, - p_req("invstring", param_string, &p->invstr), - p_opt("amount_msat", param_msat, &msat), - p_opt("label", param_string, &p->label), - p_opt_def("riskfactor", param_millionths, - &riskfactor_millionths, 1.0), - p_opt_def("retry_for", param_number, &retryfor, 60), + p_req("invstring", param_string, &invstr), + p_opt("amount_msat", param_msat, &msat), + p_opt("maxfee", param_msat, &maxfee), + + // MCF parameters + // TODO(eduardo): are these parameters read correctly? + p_opt_def("base_fee_penalty", param_millionths, &base_fee_penalty,10), + p_opt_def("prob_cost_factor", param_millionths, &prob_cost_factor,10), + p_opt_def("min_prob_success", param_millionths, + &min_prob_success_millionths,100000),// default is 10% + + p_opt_def("riskfactor", param_millionths,&riskfactor_millionths,1), + p_opt_def("maxdelay", param_number, &maxdelay, /* We're initially called to probe usage, before init! */ pay_plugin ? pay_plugin->maxdelay_default : 0), - p_opt("localofferid", param_sha256, &p->local_offer_id), - p_opt("maxfee", param_msat, &maxfee), - p_opt("description", param_string, &p->description), + + + p_opt_def("retry_for", param_number, &retryfor, 60), // 60 seconds + p_opt("localofferid", param_sha256, &local_offer_id), + p_opt("description", param_string, &description), + p_opt("label", param_string, &label), #if DEVELOPER p_opt_def("use_shadow", param_bool, &use_shadow, true), #endif NULL)) return command_param_failed(); - -#if DEVELOPER - p->use_shadow = *use_shadow; -#else - p->use_shadow = true; -#endif - - p->local_gossmods = gossmap_localmods_new(p); - p->delay_feefactor = *riskfactor_millionths / 1000000.0; + + /* renepay is bound to the command, if the command finishes renepay is + * freed. */ + struct renepay * renepay = renepay_new(cmd); + tal_add_destructor2(renepay, + renepay_cleanup, + pay_plugin->gossmap); + struct payment * p = renepay->payment; + + p->invstr = tal_steal(p,invstr); + p->description = tal_steal(p,description); + p->label = tal_steal(p,label); + p->local_offer_id = tal_steal(p,local_offer_id); + + p->base_fee_penalty = *base_fee_penalty; + base_fee_penalty = tal_free(base_fee_penalty); + + p->prob_cost_factor = *prob_cost_factor; + prob_cost_factor = tal_free(prob_cost_factor); + + p->min_prob_success = *min_prob_success_millionths/1e6; + min_prob_success_millionths = tal_free(min_prob_success_millionths); + + p->delay_feefactor = *riskfactor_millionths/1e6; + riskfactor_millionths = tal_free(riskfactor_millionths); + p->maxdelay = *maxdelay; - tal_free(maxdelay); - + maxdelay = tal_free(maxdelay); + + /* We inmediately add this payment to the payment list. */ + tal_steal(pay_plugin->ctx,p); + list_add_tail(&pay_plugin->payments, &p->list); + tal_add_destructor(p, destroy_payment); + + plugin_log(pay_plugin->plugin,LOG_DBG,"Starting renepay"); + bool gossmap_changed = gossmap_refresh(pay_plugin->gossmap, NULL); + + if (pay_plugin->gossmap == NULL) + plugin_err(pay_plugin->plugin, "Failed to refresh gossmap: %s", + strerror(errno)); + + p->start_time = time_now(); + p->stop_time = timeabs_add(p->start_time, time_from_sec(*retryfor)); + tal_free(retryfor); + + bool invstr_is_b11=false; if (!bolt12_has_prefix(p->invstr)) { struct bolt11 *b11; char *fail; @@ -973,13 +1033,14 @@ static struct command_result *json_pay(struct command *cmd, bolt11_decode(tmpctx, p->invstr, plugin_feature_set(cmd->plugin), p->description, chainparams, &fail); if (b11 == NULL) - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + return renepay_fail(renepay, JSONRPC2_INVALID_PARAMS, "Invalid bolt11: %s", fail); - + invstr_is_b11=true; + invmsat = b11->msat; invexpiry = b11->timestamp + b11->expiry; - p->dest = b11->receiver_id; + p->destination = b11->receiver_id; p->payment_hash = b11->payment_hash; p->payment_secret = tal_dup_or_null(p, struct secret, b11->payment_secret); @@ -987,7 +1048,8 @@ static struct command_result *json_pay(struct command *cmd, p->payment_metadata = tal_dup_talarr(p, u8, b11->metadata); else p->payment_metadata = NULL; - add_routehints(p, b11->routes, &p->dest); + + p->final_cltv = b11->min_final_cltv_expiry; /* Sanity check */ if (feature_offered(b11->features, OPT_VAR_ONION) && @@ -996,7 +1058,6 @@ static struct command_result *json_pay(struct command *cmd, cmd, JSONRPC2_INVALID_PARAMS, "Invalid bolt11:" " sets feature var_onion with no secret"); - /* BOLT #11: * A reader: *... @@ -1005,36 +1066,37 @@ static struct command_result *json_pay(struct command *cmd, */ if (!b11->description) { if (!b11->description_hash) { - return command_fail(cmd, + return renepay_fail(renepay, JSONRPC2_INVALID_PARAMS, "Invalid bolt11: missing description"); } if (!p->description) - return command_fail(cmd, + return renepay_fail(renepay, JSONRPC2_INVALID_PARAMS, "bolt11 uses description_hash, but you did not provide description parameter"); } } else { + // TODO(eduardo): check this, compare with `pay` const struct tlv_invoice *b12; char *fail; b12 = invoice_decode(tmpctx, p->invstr, strlen(p->invstr), plugin_feature_set(cmd->plugin), chainparams, &fail); if (b12 == NULL) - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + return renepay_fail(renepay, JSONRPC2_INVALID_PARAMS, "Invalid bolt12: %s", fail); if (!pay_plugin->exp_offers) - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + return renepay_fail(renepay, JSONRPC2_INVALID_PARAMS, "experimental-offers disabled"); if (!b12->offer_node_id) - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + return renepay_fail(renepay, JSONRPC2_INVALID_PARAMS, "invoice missing offer_node_id"); if (!b12->invoice_payment_hash) - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + return renepay_fail(renepay, JSONRPC2_INVALID_PARAMS, "invoice missing payment_hash"); if (!b12->invoice_created_at) - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + return renepay_fail(renepay, JSONRPC2_INVALID_PARAMS, "invoice missing created_at"); if (b12->invoice_amount) { invmsat = tal(cmd, struct amount_msat); @@ -1042,12 +1104,11 @@ static struct command_result *json_pay(struct command *cmd, } else invmsat = NULL; - gossmap_refresh(pay_plugin->gossmap, NULL); - node_id_from_pubkey(&p->dest, b12->offer_node_id); + node_id_from_pubkey(&p->destination, b12->offer_node_id); p->payment_hash = *b12->invoice_payment_hash; if (b12->invreq_recurrence_counter && !p->label) - return command_fail( - cmd, JSONRPC2_INVALID_PARAMS, + return renepay_fail( + renepay, JSONRPC2_INVALID_PARAMS, "recurring invoice requires a label"); /* FIXME payment_secret should be signature! */ { @@ -1077,59 +1138,600 @@ static struct command_result *json_pay(struct command *cmd, else invexpiry = *b12->invoice_created_at + BOLT12_DEFAULT_REL_EXPIRY; } + + if (node_id_eq(&pay_plugin->my_id, &p->destination)) + return renepay_fail(renepay, JSONRPC2_INVALID_PARAMS, + "This payment is destined for ourselves. " + "Self-payments are not supported"); - if (time_now().ts.tv_sec > invexpiry) - return command_fail(cmd, PAY_INVOICE_EXPIRED, "Invoice expired"); - + + // set the payment amount if (invmsat) { + // amount is written in the invoice if (msat) { - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + return renepay_fail(renepay, JSONRPC2_INVALID_PARAMS, "amount_msat parameter unnecessary"); } p->amount = *invmsat; tal_free(invmsat); } else { + // amount is not written in the invoice if (!msat) { - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + return renepay_fail(renepay, JSONRPC2_INVALID_PARAMS, "amount_msat parameter required"); } p->amount = *msat; + tal_free(msat); } - - if (node_id_eq(&pay_plugin->my_id, &p->dest)) - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "This payment is destined for ourselves. " - "Self-payments are not supported"); - + /* Default max fee is 5 sats, or 0.5%, whichever is *higher* */ if (!maxfee) { struct amount_msat fee = amount_msat_div(p->amount, 200); - if (amount_msat_less(p->amount, AMOUNT_MSAT(5000))) + if (amount_msat_less(fee, AMOUNT_MSAT(5000))) fee = AMOUNT_MSAT(5000); maxfee = tal_dup(tmpctx, struct amount_msat, &fee); } if (!amount_msat_add(&p->maxspend, p->amount, *maxfee)) { - return command_fail( - cmd, JSONRPC2_INVALID_PARAMS, + return renepay_fail( + renepay, JSONRPC2_INVALID_PARAMS, "Overflow when computing fee budget, fee far too high."); } tal_free(maxfee); + + if (time_now().ts.tv_sec > invexpiry) + return renepay_fail(renepay, PAY_INVOICE_EXPIRED, "Invoice expired"); + + + /* To construct the uncertainty network we need to perform the following + * steps: + * 1. check that there is a 1-to-1 map between channels in gossmap + * and the uncertainty network. We call `uncertainty_network_update` + * + * 2. add my local channels that could be private. + * We call `update_uncertainty_network_from_listpeerchannels`. + * + * 3. add hidden/private channels listed in the routehints. + * We call `uncertainty_network_add_routehints`. + * + * 4. check the uncertainty network invariants. + * */ + if(gossmap_changed) + uncertainty_network_update(pay_plugin->gossmap, + pay_plugin->chan_extra_map); + + + // TODO(eduardo): are there route hints for B12? + // Add any extra hidden channel revealed by the routehints to the uncertainty network. + if(invstr_is_b11) + uncertainty_network_add_routehints(pay_plugin->chan_extra_map,renepay); + + if(!uncertainty_network_check_invariants(pay_plugin->chan_extra_map)) + plugin_log(pay_plugin->plugin, + LOG_BROKEN, + "uncertainty network invariants are violated"); + + /* Next, request listsendpays for previous payments that use the same + * hash. */ + struct out_req *req + = jsonrpc_request_start(cmd->plugin, cmd, "listsendpays", + payment_listsendpays_previous, + payment_listsendpays_previous, renepay); - p->start_time = time_now(); - p->stop_time = timeabs_add(p->start_time, time_from_sec(*retryfor)); - tal_free(retryfor); + json_add_sha256(req->js, "payment_hash", &p->payment_hash); + return send_outreq(cmd->plugin, req); + + // TODO(eduardo): + // - get time since last payment, + // - forget a portion of the bounds + // - note that if sufficient time has passed, then we would forget + // everything use TIMER_FORGET_SEC. +} - /* It's now owned by the global plugin */ - list_add_tail(&pay_plugin->payments, &p->list); - tal_add_destructor(p, destroy_payment); - tal_steal(pay_plugin, p); +static void handle_sendpay_failure_renepay( + struct command *cmd, + const char *buf, + const jsmntok_t *result, + struct renepay *renepay, + struct pay_flow *flow) +{ + debug_assert(renepay); + debug_assert(flow); + struct payment *p = renepay->payment; + debug_assert(p); + + u64 errcode; + if (!json_to_u64(buf, json_get_member(buf, result, "code"), &errcode)) + { + plugin_log(pay_plugin->plugin,LOG_BROKEN, + "Failed to get code from sendpay_failure notification" + ", received json: %.*s", + json_tok_full_len(result), + json_tok_full(buf,result)); + return; + } + const jsmntok_t *msgtok = json_get_member(buf, result, "message"); + const char *message; + if(msgtok) + message = tal_fmt(tmpctx,"%.*s", + msgtok->end - msgtok->start, + buf + msgtok->start); + else + message = "[message missing from sendpay_failure notification]"; + + switch(errcode) + { + case PAY_UNPARSEABLE_ONION: + debug_paynote(p, "Unparsable onion reply on route %s", + flow_path_to_str(tmpctx, flow)); + goto unhandleable; + case PAY_TRY_OTHER_ROUTE: + break; + case PAY_DESTINATION_PERM_FAIL: + renepay_fail(renepay,errcode, + "Got a final failure from destination"); + return; + default: + renepay_fail(renepay,errcode, + "Unexpected errocode from sendpay_failure: %.*s", + json_tok_full_len(result), + json_tok_full(buf,result)); + return; + } + + const jsmntok_t* datatok = json_get_member(buf, result, "data"); + + if(!datatok) + { + plugin_err(pay_plugin->plugin, + "Failed to get data from sendpay_failure notification" + ", received json: %.*s", + json_tok_full_len(result), + json_tok_full(buf,result)); + } + + + /* OK, we expect an onion error reply. */ + u32 erridx; + const jsmntok_t * erridxtok = json_get_member(buf, datatok, "erring_index"); + if (!erridxtok || !json_to_u32(buf, erridxtok, &erridx)) + { + debug_paynote(p, "Missing erring_index reply on route %s", + flow_path_to_str(tmpctx, flow)); + plugin_log(pay_plugin->plugin,LOG_DBG, + "%s (line %d) missing erring_index " + "(erridxtok %p, erridx %d) on request %.*s", + __PRETTY_FUNCTION__,__LINE__, + erridxtok, erridx, + json_tok_full_len(result), + json_tok_full(buf,result)); + goto unhandleable; + } + + struct short_channel_id errscid; + const jsmntok_t *errchantok = json_get_member(buf, datatok, "erring_channel"); + if(!errchantok || !json_to_short_channel_id(buf, errchantok, &errscid)) + { + debug_paynote(p, "Missing erring_channel reply on route %s", + flow_path_to_str(tmpctx, flow)); + goto unhandleable; + } + + if (erridxpath_scids) + && !short_channel_id_eq(&errscid, &flow->path_scids[erridx])) + { + debug_paynote(p, + "erring_index (%d) does not correspond" + "to erring_channel (%s) on route %s", + erridx, + type_to_string(tmpctx,struct short_channel_id,&errscid), + flow_path_to_str(tmpctx,flow)); + goto unhandleable; + } + + u32 onionerr; + const jsmntok_t *failcodetok = json_get_member(buf, datatok, "failcode"); + if(!failcodetok || !json_to_u32(buf, failcodetok, &onionerr)) + { + // TODO(eduardo): I wonder which error code should I show the + // user in this case? + renepay_fail(renepay,LIGHTNINGD, + "Failed to get failcode from sendpay_failure notification" + ", received json: %.*s", + json_tok_full_len(result), + json_tok_full(buf,result)); + return; + } + + debug_paynote(p, + "onion error %s from node #%u %s: %s", + onion_wire_name(onionerr), + erridx, + type_to_string(tmpctx, struct short_channel_id, &errscid), + message); + + const jsmntok_t *rawoniontok = json_get_member(buf, datatok, "raw_message"); + if(!rawoniontok) + goto unhandleable; + + switch ((enum onion_wire)onionerr) { + /* These definitely mean eliminate channel */ + case WIRE_PERMANENT_CHANNEL_FAILURE: + case WIRE_REQUIRED_CHANNEL_FEATURE_MISSING: + /* FIXME: lnd returns this for disconnected peer, so don't disable perm! */ + case WIRE_UNKNOWN_NEXT_PEER: + case WIRE_CHANNEL_DISABLED: + /* These mean node is weird, but we eliminate channel here too */ + case WIRE_INVALID_REALM: + case WIRE_TEMPORARY_NODE_FAILURE: + case WIRE_PERMANENT_NODE_FAILURE: + case WIRE_REQUIRED_NODE_FEATURE_MISSING: + /* These shouldn't happen, but eliminate channel */ + case WIRE_INVALID_ONION_VERSION: + case WIRE_INVALID_ONION_HMAC: + case WIRE_INVALID_ONION_KEY: + case WIRE_INVALID_ONION_PAYLOAD: + case WIRE_INVALID_ONION_BLINDING: + case WIRE_EXPIRY_TOO_FAR: + debug_paynote(p, "we're removing scid %s", + type_to_string(tmpctx,struct short_channel_id,&errscid)); + tal_arr_expand(&renepay->disabled, errscid); + return; - /* Get local capacities... */ - req = jsonrpc_request_start(cmd->plugin, cmd, "listpeers", - listpeers_done, - listpeers_done, p); - return send_outreq(cmd->plugin, req); + /* These can be fixed (maybe) by applying the included channel_update */ + case WIRE_AMOUNT_BELOW_MINIMUM: + case WIRE_FEE_INSUFFICIENT: + case WIRE_INCORRECT_CLTV_EXPIRY: + case WIRE_EXPIRY_TOO_SOON: + plugin_log(pay_plugin->plugin,LOG_DBG,"sendpay_failure, apply channel_update"); + /* FIXME: Check scid! */ + // TODO(eduardo): check + const u8 *update = channel_update_from_onion_error(tmpctx, buf, rawoniontok); + if (update) + { + submit_update(cmd, flow, update, errscid); + return; + } + + debug_paynote(p, "missing an update, so we're removing scid %s", + type_to_string(tmpctx,struct short_channel_id,&errscid)); + tal_arr_expand(&renepay->disabled, errscid); + return; + + case WIRE_TEMPORARY_CHANNEL_FAILURE: + case WIRE_MPP_TIMEOUT: + return; + + /* These are from the final distination: fail */ + case WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS: + case WIRE_FINAL_INCORRECT_CLTV_EXPIRY: + case WIRE_FINAL_INCORRECT_HTLC_AMOUNT: + debug_paynote(p,"final destination failure"); + renepay_fail(renepay,errcode, + "Destination said %s: %s", + onion_wire_name(onionerr), + message); + return; + } + + debug_assert(erridx<=tal_count(flow->path_nodes)); + + if(erridx == tal_count(flow->path_nodes)) + { + debug_paynote(p,"unkown onion error code %u, fatal", + onionerr); + renepay_fail(renepay,errcode, + "Destination gave unknown error code %u: %s", + onionerr,message); + return; + }else + { + debug_paynote(p,"unkown onion error code %u, removing scid %s", + onionerr, + type_to_string(tmpctx,struct short_channel_id,&errscid)); + tal_arr_expand(&renepay->disabled, errscid); + return; + } + unhandleable: + // TODO(eduardo): check + handle_unhandleable_error(renepay, flow, ""); +} + +static void handle_sendpay_failure_flow( + struct command *cmd, + const char *buf, + const jsmntok_t *result, + struct pay_flow *flow) +{ + // TODO(eduardo): review with Rusty the level of severity of the + // different cases of error below. + debug_assert(flow); + + struct payment * const p = flow->payment; + payment_fail(p); + + u64 errcode; + if (!json_to_u64(buf, json_get_member(buf, result, "code"), &errcode)) + { + plugin_log(pay_plugin->plugin,LOG_BROKEN, + "Failed to get code from sendpay_failure notification" + ", received json: %.*s", + json_tok_full_len(result), + json_tok_full(buf,result)); + return; + } + const jsmntok_t *msgtok = json_get_member(buf, result, "message"); + const char *message; + if(msgtok) + message = tal_fmt(tmpctx,"%.*s", + msgtok->end - msgtok->start, + buf + msgtok->start); + else + message = "[message missing from sendpay_failure notification]"; + + if(errcode!=PAY_TRY_OTHER_ROUTE) + return; + + const jsmntok_t* datatok = json_get_member(buf, result, "data"); + if(!datatok) + { + plugin_err(pay_plugin->plugin, + "Failed to get data from sendpay_failure notification" + ", received json: %.*s", + json_tok_full_len(result), + json_tok_full(buf,result)); + } + + /* OK, we expect an onion error reply. */ + u32 erridx; + const jsmntok_t * erridxtok = json_get_member(buf, datatok, "erring_index"); + if (!erridxtok || !json_to_u32(buf, erridxtok, &erridx)) + { + plugin_log(pay_plugin->plugin,LOG_BROKEN, + "Failed to get erring_index from sendpay_failure notification" + ", received json: %.*s", + json_tok_full_len(result), + json_tok_full(buf,result)); + return; + } + + struct short_channel_id errscid; + const jsmntok_t *errchantok = json_get_member(buf, datatok, "erring_channel"); + if(!errchantok || !json_to_short_channel_id(buf, errchantok, &errscid)) + { + plugin_log(pay_plugin->plugin,LOG_BROKEN, + "Failed to get erring_channel from sendpay_failure notification" + ", received json: %.*s", + json_tok_full_len(result), + json_tok_full(buf,result)); + return; + } + + if (erridxpath_scids) + && !short_channel_id_eq(&errscid, &flow->path_scids[erridx])) + { + plugin_err(pay_plugin->plugin, + "Erring channel %u/%zu was %s not %s (path %s)", + erridx, tal_count(flow->path_scids), + type_to_string(tmpctx, + struct short_channel_id, + &errscid), + type_to_string(tmpctx, + struct short_channel_id, + &flow->path_scids[erridx]), + flow_path_to_str(tmpctx, flow)); + return; + } + + + u32 onionerr; + const jsmntok_t *failcodetok = json_get_member(buf, datatok, "failcode"); + if(!failcodetok || !json_to_u32(buf, failcodetok, &onionerr)) + { + plugin_log(pay_plugin->plugin,LOG_BROKEN, + "Failed to get failcode from sendpay_failure notification" + ", received json: %.*s", + json_tok_full_len(result), + json_tok_full(buf,result)); + return; + + } + + plugin_log(pay_plugin->plugin,LOG_UNUSUAL, + "onion error %s from node #%u %s: " + "%s", + onion_wire_name(onionerr), + erridx, + type_to_string(tmpctx, struct short_channel_id, &errscid), + message); + + /* we know that all channels before erridx where able to commit to this payment */ + uncertainty_network_channel_can_send( + pay_plugin->chan_extra_map, + flow, + erridx); + + /* Insufficient funds! */ + if((enum onion_wire)onionerr == WIRE_TEMPORARY_CHANNEL_FAILURE) + { + plugin_log(pay_plugin->plugin,LOG_DBG, + "sendpay_failure says insufficient funds!"); + + chan_extra_cannot_send(p,pay_plugin->chan_extra_map, + flow->path_scids[erridx], + flow->path_dirs[erridx], + /* This channel can't send all that was + * commited in HTLCs. + * Had we removed the commited amount then + * we would have to put here flow->amounts[erridx]. */ + AMOUNT_MSAT(0)); + } +} + +static struct command_result *notification_shutdown(struct command *cmd, + const char *buf, + const jsmntok_t *params) +{ + /* TODO(eduardo): + * 1. at shutdown the `struct plugin *p` is not freed, + * 2. `memleak_check` is called before we have the chance to get this + * notification. */ + plugin_log(pay_plugin->plugin,LOG_DBG,"received shutdown notification, freeing data."); + pay_plugin->ctx = tal_free(pay_plugin->ctx); + return notification_handled(cmd); +} +static struct command_result *notification_sendpay_success( + struct command *cmd, + const char *buf, + const jsmntok_t *params) +{ + struct pay_flow *flow = NULL; + const jsmntok_t *resulttok = json_get_member(buf,params,"sendpay_success"); + if(!resulttok) + debug_err("Failed to get result from sendpay_success notification" + ", received json: %.*s", + json_tok_full_len(params), + json_tok_full(buf,params)); + + // 1. generate the key of this payflow + struct payflow_key key; + key.payment_hash = tal(tmpctx,struct sha256); + + const jsmntok_t *parttok = json_get_member(buf,resulttok,"partid"); + if(!parttok || !json_to_u64(buf,parttok,&key.partid)) + { + // No partid, is this a single-path payment? + key.partid = 0; + // debug_err("Failed to get partid from sendpay_success notification" + // ", received json: %.*s", + // json_tok_full_len(params), + // json_tok_full(buf,params)); + } + const jsmntok_t *grouptok = json_get_member(buf,resulttok,"groupid"); + if(!grouptok || !json_to_u64(buf,grouptok,&key.groupid)) + debug_err("Failed to get groupid from sendpay_success notification" + ", received json: %.*s", + json_tok_full_len(params), + json_tok_full(buf,params)); + + const jsmntok_t *hashtok = json_get_member(buf,resulttok,"payment_hash"); + if(!hashtok || !json_to_sha256(buf,hashtok,key.payment_hash)) + debug_err("Failed to get payment_hash from sendpay_success notification" + ", received json: %.*s", + json_tok_full_len(params), + json_tok_full(buf,params)); + + plugin_log(pay_plugin->plugin,LOG_DBG, + "I received a sendpay_success with key %s", + fmt_payflow_key(tmpctx,&key)); + + // 2. is this payflow recorded in renepay? + flow = payflow_map_get(pay_plugin->payflow_map,key); + if(!flow) + { + plugin_log(pay_plugin->plugin,LOG_DBG, + "sendpay_success does not correspond to a renepay attempt, %s", + fmt_payflow_key(tmpctx,&key)); + goto done; + } + + // 3. mark as success + struct payment * const p = flow->payment; + debug_assert(p); + + payment_success(p); + + const jsmntok_t *preimagetok + = json_get_member(buf, resulttok, "payment_preimage"); + struct preimage preimage; + + if (!preimagetok || !json_to_preimage(buf, preimagetok,&preimage)) + debug_err("Failed to get payment_preimage from sendpay_success notification" + ", received json: %.*s", + json_tok_full_len(params), + json_tok_full(buf,params)); + + p->preimage = tal_dup_or_null(p,struct preimage,&preimage); + + // 4. update information and release pending HTLCs + uncertainty_network_flow_success(pay_plugin->chan_extra_map,flow); + + done: + tal_free(flow); + return notification_handled(cmd); +} +static struct command_result *notification_sendpay_failure( + struct command *cmd, + const char *buf, + const jsmntok_t *params) +{ + struct pay_flow *flow = NULL; + + const jsmntok_t *resulttok = json_get_member(buf,params,"sendpay_failure"); + if(!resulttok) + debug_err("Failed to get result from sendpay_failure notification" + ", received json: %.*s", + json_tok_full_len(params), + json_tok_full(buf,params)); + + const jsmntok_t *datatok = json_get_member(buf,resulttok,"data"); + if(!datatok) + debug_err("Failed to get data from sendpay_failure notification" + ", received json: %.*s", + json_tok_full_len(params), + json_tok_full(buf,params)); + + + // 1. generate the key of this payflow + struct payflow_key key; + key.payment_hash = tal(tmpctx,struct sha256); + + const jsmntok_t *parttok = json_get_member(buf,datatok,"partid"); + if(!parttok || !json_to_u64(buf,parttok,&key.partid)) + { + // No partid, is this a single-path payment? + key.partid = 0; + } + const jsmntok_t *grouptok = json_get_member(buf,datatok,"groupid"); + if(!grouptok || !json_to_u64(buf,grouptok,&key.groupid)) + debug_err("Failed to get groupid from sendpay_failure notification" + ", received json: %.*s", + json_tok_full_len(params), + json_tok_full(buf,params)); + + const jsmntok_t *hashtok = json_get_member(buf,datatok,"payment_hash"); + if(!hashtok || !json_to_sha256(buf,hashtok,key.payment_hash)) + debug_err("Failed to get payment_hash from sendpay_failure notification" + ", received json: %.*s", + json_tok_full_len(params), + json_tok_full(buf,params)); + + plugin_log(pay_plugin->plugin,LOG_DBG, + "I received a sendpay_failure with key %s", + fmt_payflow_key(tmpctx,&key)); + + // 2. is this payflow recorded in renepay? + flow = payflow_map_get(pay_plugin->payflow_map,key); + if(!flow) + { + plugin_log(pay_plugin->plugin,LOG_DBG, + "sendpay_failure does not correspond to a renepay attempt, %s", + fmt_payflow_key(tmpctx,&key)); + goto done; + } + + // 3. process failure + handle_sendpay_failure_flow(cmd,buf,resulttok,flow); + + // there is possibly a pending renepay command for this flow + struct renepay * const renepay = flow->payment->renepay; + + if(renepay) + handle_sendpay_failure_renepay(cmd,buf,resulttok,renepay,flow); + + done: + if(flow) payflow_fail(flow); + return notification_handled(cmd); } static const struct plugin_command commands[] = { @@ -1149,10 +1751,43 @@ static const struct plugin_command commands[] = { }, }; +static const struct plugin_notification notifications[] = { + { + "shutdown", + notification_shutdown, + }, + { + "sendpay_success", + notification_sendpay_success, + }, + { + "sendpay_failure", + notification_sendpay_failure, + } +}; + int main(int argc, char *argv[]) { setup_locale(); - plugin_main(argv, init, PLUGIN_RESTARTABLE, true, NULL, commands, - ARRAY_SIZE(commands), NULL, 0, NULL, 0, NULL, 0, - NULL); + plugin_main( + argv, + init, + PLUGIN_RESTARTABLE, + /* init_rpc */ true, + /* features */ NULL, + commands, ARRAY_SIZE(commands), + notifications, ARRAY_SIZE(notifications), + /* hooks */ NULL, 0, + /* notification topics */ NULL, 0, + plugin_option("renepay-debug-mcf", "flag", + "Enable renepay MCF debug info.", + flag_option, &pay_plugin->debug_mcf), + plugin_option("renepay-debug-payflow", "flag", + "Enable renepay payment flows debug info.", + flag_option, &pay_plugin->debug_payflow), + NULL); + + // TODO(eduardo): I think this is actually never executed + tal_free(pay_plugin->ctx); + return 0; } diff --git a/plugins/renepay/pay.h b/plugins/renepay/pay.h index 803c6f0154fd..82b48bcde90d 100644 --- a/plugins/renepay/pay.h +++ b/plugins/renepay/pay.h @@ -3,7 +3,52 @@ #include "config.h" #include #include +#include #include +#include +#include + +// TODO(eduardo): renepaystatus should be similar to paystatus + +// TODO(eduardo): MCF should consider pending HTLCs occupy some capacity in the +// routing channels. + +// TODO(eduardo): for some reason cln-renepay does not terminate inmediately: +// 2023-06-26T07:32:36.064Z DEBUG lightningd: cln-renepay: failed to self-terminate in time, killing. +// +// TODO(eduardo): check a problem with param_millionths(), if I input an integer +// should or shouldn't be multiplied by 10^6? +// TODO(eduardo): add an option entry for maxfeepercent +// TODO(eduardo): write a man entry for renepay +// TODO(eduardo): check if paynotes are meaningful +// TODO(eduardo): remove assertions, introduce LOG_BROKEN messages + +#define MAX_NUM_ATTEMPTS 10 + +/* Time lapse used to wait for failed sendpays before try_paying. */ +#define TIMER_COLLECT_FAILURES_MSEC 250 + +/* Knowledge is proportionally decreased with time up to TIMER_FORGET_SEC when + * we forget everything. */ +#define TIMER_FORGET_SEC 3600 + +// TODO(eduardo): Test ideas +// - make a payment to a node that is hidden behind private channels, check that +// private channels are removed from the gossmap and chan_extra_map +// - one payment route hangs, and the rest keep waiting, eventually all MPP +// should timeout and we retry excluding the unresponsive path (are we able to +// identify it?) +// - a particular route fails because fees are wrong, we update the gossip +// information and redo the path. +// - a MPP in which several parts have a common intermediary node +// source -MANY- o -MANY- dest +// - a MPP in which several parts have a common intermediary channel +// source -MANY- o--o -MANY- dest +// - a payment with a direct channel to the destination +// - payment failures: +// - destination is not in the gossmap +// - destination is offline +// - with current knowledge there is no flow solution to destination /* Our convenient global data, here in one place. */ struct pay_plugin { @@ -26,78 +71,39 @@ struct pay_plugin { struct list_head payments; /* Per-channel metadata: some persists between payments */ - struct chan_extra_map chan_extra_map; -}; - -/* Set in init */ -extern struct pay_plugin *pay_plugin; - -struct payment { - /* Inside pay_plugin->payments list */ - struct list_node list; - - /* The command, and our owner (needed for timer func) */ - struct command *cmd; - - /* We promised this in pay() output */ - struct timeabs start_time; - - /* Localmods to apply to gossip_map for our own use. */ - struct gossmap_localmods *local_gossmods; - - /* invstring (bolt11 or bolt12) */ - const char *invstr; - - /* How much, what, where */ - struct node_id dest; - struct sha256 payment_hash; - struct amount_msat amount; - u32 final_cltv; - - /* Total sent, including fees. */ - struct amount_msat total_sent; - /* Total that is delivering (i.e. without fees) */ - struct amount_msat total_delivering; - - /* payment_secret, if specified by invoice. */ - struct secret *payment_secret; - /* Payment metadata, if specified by invoice. */ - const u8 *payment_metadata; - - /* Description and labels, if any. */ - const char *description, *label; - - /* Penalty for CLTV delays */ - double delay_feefactor; - - /* Limits on what routes we'll accept. */ - struct amount_msat maxspend; - unsigned int maxdelay; - struct timeabs stop_time; - - /* Channels we decided to disable for various reasons. */ - struct short_channel_id *disabled; - - /* Chatty description of attempts. */ - const char **paynotes; - - /* Groupid, so listpays() can group them back together */ - u64 groupid; - u64 next_partid; - /* If this is paying a local offer, this is the one (sendpay ensures we - * don't pay twice for single-use offers) */ - struct sha256 *local_offer_id; - + struct chan_extra_map *chan_extra_map; + + /* Pending senpays. */ + struct payflow_map * payflow_map; + + bool debug_mcf; + bool debug_payflow; + + /* I'll allocate all global (controlled by pay_plugin) variables tied to + * this tal_t. */ + tal_t *ctx; + + // TODO(eduardo): pending flows have HTLCs (in-flight) liquidity + // attached that is reflected in the uncertainty network. When + // waitsendpay returns either fail or success that flow is destroyed and + // the liquidity is restored. A payment command could end before all + // flows are destroyed, therefore it is important to delegate the + // ownership of the waitsendpay request to pay_plugin->ctx so that the + // request is kept alive. One more thing: to double check that flows are + // not accumulating ad-infinitum I would insert them into a data + // structure here so that once in a while a timer kicks and verifies the + // list of pending flows. + // TODO(eduardo): notice that pending attempts performed with another + // pay plugin are not considered by the uncertainty network in renepay, + // it would be nice if listsendpay would give us the route of pending + // sendpays. + /* Timers. */ struct plugin_timer *rexmit_timer; - - /* DEVELOPER allows disabling shadow route */ - bool use_shadow; }; -void paynote(struct payment *p, const char *fmt, ...) - PRINTF_FMT(2,3); - +/* Set in init */ +extern struct pay_plugin * const pay_plugin; /* Accumulate or panic on overflow */ #define amount_msat_accumulate(dst, src) \ diff --git a/plugins/renepay/pay_flow.c b/plugins/renepay/pay_flow.c index 3bdf34fdee91..5bce761da538 100644 --- a/plugins/renepay/pay_flow.c +++ b/plugins/renepay/pay_flow.c @@ -8,6 +8,8 @@ #include #include #include +#include +#include /* BOLT #7: * @@ -83,7 +85,7 @@ static u32 shadow_one_flow(const struct gossmap *gossmap, ; /* Shouldn't happen either */ - if (amount_msat_sub(shadow_fee, amount, f->amounts[numpath-1])) + if (!amount_msat_sub(shadow_fee, amount, f->amounts[numpath-1])) plugin_err(pay_plugin->plugin, "Failed to calc shadow fee: %s - %s", type_to_string(tmpctx, struct amount_msat, &amount), @@ -135,10 +137,11 @@ static u64 flow_delay(const struct flow *flow) /* This enhances f->amounts, and returns per-flow cltvs */ static u32 *shadow_additions(const tal_t *ctx, const struct gossmap *gossmap, - struct payment *p, + struct renepay *renepay, struct flow **flows, bool is_entire_payment) { + struct payment * p = renepay->payment; u32 *final_cltvs; /* Set these up now in case we decide to do nothing */ @@ -157,7 +160,7 @@ static u32 *shadow_additions(const tal_t *ctx, shadow_delay = shadow_one_flow(gossmap, flows[i], &shadow_fee); if (flow_delay(flows[i]) + shadow_delay > p->maxdelay) { - paynote(p, "No shadow for flow %zu/%zu:" + debug_paynote(p, "No shadow for flow %zu/%zu:" " delay would add %u to %"PRIu64", exceeding max delay.", i, tal_count(flows), shadow_delay, @@ -172,7 +175,7 @@ static u32 *shadow_additions(const tal_t *ctx, if (is_entire_payment && tal_count(flows) == 1) { if (!add_to_amounts(gossmap, flows[i], p->maxspend, shadow_fee)) { - paynote(p, "No shadow fee for flow %zu/%zu:" + debug_paynote(p, "No shadow fee for flow %zu/%zu:" " fee would add %s to %s, exceeding budget %s.", i, tal_count(flows), type_to_string(tmpctx, struct amount_msat, @@ -182,14 +185,14 @@ static u32 *shadow_additions(const tal_t *ctx, type_to_string(tmpctx, struct amount_msat, &p->maxspend)); } else { - paynote(p, "No MPP, so added %s shadow fee", + debug_paynote(p, "No MPP, so added %s shadow fee", type_to_string(tmpctx, struct amount_msat, &shadow_fee)); } } final_cltvs[i] += shadow_delay; - paynote(p, "Shadow route on flow %zu/%zu added %u block delay. now %u", + debug_paynote(p, "Shadow route on flow %zu/%zu added %u block delay. now %u", i, tal_count(flows), shadow_delay, final_cltvs[i]); } @@ -214,8 +217,12 @@ static struct pay_flow **flows_to_pay_flows(struct payment *payment, plen = tal_count(f->path); pay_flows[i] = pf; + pf->payment = payment; - pf->partid = (*next_partid)++; + pf->key.partid = (*next_partid)++; + pf->key.groupid = payment->groupid; + pf->key.payment_hash = &payment->payment_hash; + /* Convert gossmap_chan into scids and nodes */ pf->path_scids = tal_arr(pf, struct short_channel_id, plen); pf->path_nodes = tal_arr(pf, struct node_id, plen); @@ -237,6 +244,7 @@ static struct pay_flow **flows_to_pay_flows(struct payment *payment, pf->amounts = tal_steal(pf, f->amounts); pf->path_dirs = tal_steal(pf, f->dirs); pf->success_prob = f->success_prob; + pf->attempt = renepay_current_attempt(payment->renepay); } tal_free(flows); @@ -259,32 +267,6 @@ static bitmap *make_disabled_bitmap(const tal_t *ctx, return disabled; } -static double flows_probability(struct flow **flows) -{ - double prob = 1.0; - - for (size_t i = 0; i < tal_count(flows); i++) - prob *= flows[i]->success_prob; - - return prob; -} - -static struct amount_msat flows_fee(struct flow **flows) -{ - struct amount_msat fee = AMOUNT_MSAT(0); - - for (size_t i = 0; i < tal_count(flows); i++) { - struct amount_msat this_fee; - size_t n = tal_count(flows[i]->amounts); - - if (!amount_msat_sub(&this_fee, - flows[i]->amounts[0], - flows[i]->amounts[n-1])) - abort(); - amount_msat_accumulate(&fee, this_fee); - } - return fee; -} static u64 flows_worst_delay(struct flow **flows) { @@ -298,11 +280,12 @@ static u64 flows_worst_delay(struct flow **flows) } /* FIXME: If only path has channels marked disabled, we should try... */ -static bool disable_htlc_violations_oneflow(struct payment *p, +static bool disable_htlc_violations_oneflow(struct renepay * renepay, const struct flow *flow, const struct gossmap *gossmap, bitmap *disabled) { + struct payment * p = renepay->payment; bool disabled_some = false; for (size_t i = 0; i < tal_count(flow->path); i++) { @@ -320,12 +303,12 @@ static bool disable_htlc_violations_oneflow(struct payment *p, continue; scid = gossmap_chan_scid(gossmap, flow->path[i]); - paynote(p, "...disabling channel %s: %s", + debug_paynote(p, "...disabling channel %s: %s", type_to_string(tmpctx, struct short_channel_id, &scid), reason); /* Add this for future searches for this payment. */ - tal_arr_expand(&p->disabled, scid); + tal_arr_expand(&renepay->disabled, scid); /* Add to existing bitmap */ bitmap_set_bit(disabled, gossmap_chan_idx(gossmap, flow->path[i])); @@ -336,7 +319,7 @@ static bool disable_htlc_violations_oneflow(struct payment *p, /* If we can't use one of these flows because we hit limits, we disable that * channel for future searches and return false */ -static bool disable_htlc_violations(struct payment *p, +static bool disable_htlc_violations(struct renepay *renepay, struct flow **flows, const struct gossmap *gossmap, bitmap *disabled) @@ -345,49 +328,39 @@ static bool disable_htlc_violations(struct payment *p, /* We continue through all of them, to disable many at once. */ for (size_t i = 0; i < tal_count(flows); i++) { - disabled_some |= disable_htlc_violations_oneflow(p, flows[i], + disabled_some |= disable_htlc_violations_oneflow(renepay, flows[i], gossmap, disabled); } return disabled_some; } -/* We're not using these flows after all: clean up chan_extra_map */ -static void remove_flows(const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map, - struct flow **flows) -{ - for (size_t i = 0; i < tal_count(flows); i++) - remove_completed_flow(gossmap, chan_extra_map, flows[i]); -} - /* Get some payment flows to get this amount to destination, or NULL. */ -struct pay_flow **get_payflows(struct payment *p, +struct pay_flow **get_payflows(struct renepay * renepay, struct amount_msat amount, struct amount_msat feebudget, bool unlikely_ok, - bool is_entire_payment) + bool is_entire_payment, + char const ** err_msg) { - double frugality = 1.0; - bool was_too_expensive = false; + *err_msg = tal_fmt(tmpctx,"[no error]"); + + struct payment * p = renepay->payment; bitmap *disabled; struct pay_flow **pay_flows; const struct gossmap_node *src, *dst; - - if (!gossmap_refresh(pay_plugin->gossmap, NULL)) - plugin_err(pay_plugin->plugin, "Failed to refresh gossmap: %s", - strerror(errno)); - - gossmap_apply_localmods(pay_plugin->gossmap, p->local_gossmods); - disabled = make_disabled_bitmap(tmpctx, pay_plugin->gossmap, p->disabled); + + disabled = make_disabled_bitmap(tmpctx, pay_plugin->gossmap, renepay->disabled); src = gossmap_find_node(pay_plugin->gossmap, &pay_plugin->my_id); if (!src) { - paynote(p, "We don't have any channels?"); + debug_paynote(p, "We don't have any channels?"); + *err_msg = tal_fmt(tmpctx,"We don't have any channels."); goto fail; } - dst = gossmap_find_node(pay_plugin->gossmap, &p->dest); - if (!src) { - paynote(p, "No trace of destination in network gossip"); + dst = gossmap_find_node(pay_plugin->gossmap, &p->destination); + if (!dst) { + debug_paynote(p, "No trace of destination in network gossip"); + *err_msg = tal_fmt(tmpctx,"Destination is unreacheable in the network gossip."); goto fail; } @@ -398,131 +371,263 @@ struct pay_flow **get_payflows(struct payment *p, u64 delay; bool too_unlikely, too_expensive, too_delayed; const u32 *final_cltvs; - - /* Note! This actually puts flows in chan_extra_map, so - * flows must be removed if not used! */ + flows = minflow(tmpctx, pay_plugin->gossmap, src, dst, - &pay_plugin->chan_extra_map, disabled, - amount, - frugality, - p->delay_feefactor); + pay_plugin->chan_extra_map, disabled, + amount, + feebudget, + p->min_prob_success , + p->delay_feefactor, + p->base_fee_penalty, + p->prob_cost_factor); if (!flows) { - paynote(p, "Failed to find any paths for %s", - type_to_string(tmpctx, - struct amount_msat, - &amount)); + debug_paynote(p, + "minflow couldn't find a feasible flow for %s", + type_to_string(tmpctx,struct amount_msat,&amount)); + + *err_msg = tal_fmt(tmpctx, + "minflow couldn't find a feasible flow for %s", + type_to_string(tmpctx,struct amount_msat,&amount)); goto fail; } /* Are we unhappy? */ - prob = flows_probability(flows); - fee = flows_fee(flows); + prob = flow_set_probability(flows,pay_plugin->gossmap,pay_plugin->chan_extra_map); + fee = flow_set_fee(flows); delay = flows_worst_delay(flows) + p->final_cltv; - too_unlikely = (prob < 0.01); - if (too_unlikely) - paynote(p, "Flows too unlikely, P() = %f%%", prob * 100); - + too_unlikely = (prob < p->min_prob_success); + if (too_unlikely && !unlikely_ok) + { + debug_paynote(p, "Flows too unlikely, P() = %f%%", prob * 100); + *err_msg = tal_fmt(tmpctx, + "Probability is too small, " + "Prob = %f%% (min = %f%%)", + prob*100, + p->min_prob_success*100); + goto fail; + } too_expensive = amount_msat_greater(fee, feebudget); if (too_expensive) - paynote(p, "Flows too expensive, fee = %s (max %s)", + { + debug_paynote(p, "Flows too expensive, fee = %s (max %s)", type_to_string(tmpctx, struct amount_msat, &fee), type_to_string(tmpctx, struct amount_msat, &feebudget)); - + *err_msg = tal_fmt(tmpctx, + "Fee exceeds our fee budget, " + "fee = %s (maxfee = %s)", + type_to_string(tmpctx, struct amount_msat, &fee), + type_to_string(tmpctx, struct amount_msat, &feebudget)); + goto fail; + } too_delayed = (delay > p->maxdelay); if (too_delayed) { - paynote(p, "Flows too delayed, delay = %"PRIu64" (max %u)", + debug_paynote(p, "Flows too delayed, delay = %"PRIu64" (max %u)", delay, p->maxdelay); + /* FIXME: What is a sane limit? */ if (p->delay_feefactor > 1000) { - paynote(p, "Giving up!"); - goto fail_path; + debug_paynote(p, "Giving up!"); + *err_msg = tal_fmt(tmpctx, + "CLTV delay exceeds our CLTV budget, " + "delay = %"PRIu64" (maxdelay = %u)", + delay,p->maxdelay); + goto fail; } p->delay_feefactor *= 2; - paynote(p, "Doubling delay_feefactor to %f", + debug_paynote(p, "Doubling delay_feefactor to %f", p->delay_feefactor); + + continue; // retry } - /* too expensive vs too unlikely is a tradeoff... */ - if (too_expensive) { - if (too_unlikely && !unlikely_ok) { - paynote(p, "Giving up!"); - goto fail_path; - } - - /* Try increasing frugality? */ - if (frugality >= 32) { - paynote(p, "Still too expensive, giving up!"); - goto fail_path; - } - frugality *= 2; - was_too_expensive = true; - paynote(p, "... retry with frugality increased to %f", - frugality); - goto retry; - } else if (too_unlikely) { - /* Are we bouncing between "too expensive" and - * "too unlikely"? */ - if (was_too_expensive) { - paynote(p, "Bouncing between expensive and unlikely"); - if (!unlikely_ok) - goto fail_path; - paynote(p, "... picking unlikely"); - goto seems_ok; - } - - /* Try decreasing frugality? */ - if (frugality < 0.01) { - paynote(p, "Still too unlikely, giving up!"); - goto fail_path; - } - frugality /= 2; - paynote(p, "... retry with frugality reduced to %f", - frugality); - goto retry; - } - - seems_ok: /* Now we check for min/max htlc violations, and * excessive htlc counts. It would be more efficient * to do this inside minflow(), but the diagnostics here * are far better, since we can report min/max which * *actually* made us reconsider. */ - if (disable_htlc_violations(p, flows, pay_plugin->gossmap, + if (disable_htlc_violations(renepay, flows, pay_plugin->gossmap, disabled)) - goto retry; - + { + continue; // retry + } + /* This can adjust amounts and final cltv for each flow, * to make it look like it's going elsewhere */ final_cltvs = shadow_additions(tmpctx, pay_plugin->gossmap, - p, flows, is_entire_payment); - + renepay, flows, is_entire_payment); /* OK, we are happy with these flows: convert to * pay_flows to outlive the current gossmap. */ - pay_flows = flows_to_pay_flows(p, pay_plugin->gossmap, + pay_flows = flows_to_pay_flows(renepay->payment, pay_plugin->gossmap, flows, final_cltvs, - &p->next_partid); + &renepay->next_partid); break; - - retry: - remove_flows(pay_plugin->gossmap, &pay_plugin->chan_extra_map, - flows); - continue; - fail_path: - remove_flows(pay_plugin->gossmap, &pay_plugin->chan_extra_map, - flows); - goto fail; } -out: - /* Always remove our local mods (routehints) so others can use - * gossmap */ - gossmap_remove_localmods(pay_plugin->gossmap, p->local_gossmods); return pay_flows; fail: - pay_flows = NULL; - goto out; + return NULL; +} + +const char *flow_path_to_str(const tal_t *ctx, const struct pay_flow *flow) +{ + char *s = tal_strdup(ctx, ""); + for (size_t i = 0; i < tal_count(flow->path_scids); i++) { + tal_append_fmt(&s, "-%s->", + type_to_string(tmpctx, struct short_channel_id, + &flow->path_scids[i])); + } + return s; +} + +const char* fmt_payflows(const tal_t *ctx, + struct pay_flow ** flows) +{ + struct json_out *jout = json_out_new(ctx); + json_out_start(jout, NULL, '{'); + json_out_start(jout,"Pay_flows",'['); + + for(size_t i=0;isuccess_prob); + + json_out_start(jout,"path_scids",'['); + for(size_t j=0;jpath_scids);++j) + { + json_out_add(jout,NULL,true,"%s", + type_to_string(ctx,struct short_channel_id,&f->path_scids[j])); + } + json_out_end(jout,']'); + + json_out_start(jout,"path_dirs",'['); + for(size_t j=0;jpath_dirs);++j) + { + json_out_add(jout,NULL,false,"%d",f->path_dirs[j]); + } + json_out_end(jout,']'); + + json_out_start(jout,"amounts",'['); + for(size_t j=0;jamounts);++j) + { + json_out_add(jout,NULL,true,"%s", + type_to_string(ctx,struct amount_msat,&f->amounts[j])); + } + json_out_end(jout,']'); + + json_out_start(jout,"cltv_delays",'['); + for(size_t j=0;jcltv_delays);++j) + { + json_out_add(jout,NULL,false,"%d",f->cltv_delays[j]); + } + json_out_end(jout,']'); + + json_out_start(jout,"path_nodes",'['); + for(size_t j=0;jpath_nodes);++j) + { + json_out_add(jout,NULL,true,"%s", + type_to_string(ctx,struct node_id,&f->path_nodes[j])); + } + json_out_end(jout,']'); + + json_out_end(jout,'}'); + } + + json_out_end(jout,']'); + json_out_end(jout, '}'); + json_out_direct(jout, 1)[0] = '\n'; + json_out_direct(jout, 1)[0] = '\0'; + json_out_finished(jout); + + size_t len; + return json_out_contents(jout,&len); } +void remove_htlc_payflow( + struct chan_extra_map *chan_extra_map, + struct pay_flow *flow) +{ + for (size_t i = 0; i < tal_count(flow->path_scids); i++) { + struct chan_extra_half *h = get_chan_extra_half_by_scid( + chan_extra_map, + flow->path_scids[i], + flow->path_dirs[i]); + if(!h) + { + plugin_err(pay_plugin->plugin, + "%s could not resolve chan_extra_half", + __PRETTY_FUNCTION__); + } + if (!amount_msat_sub(&h->htlc_total, h->htlc_total, flow->amounts[i])) + { + plugin_err(pay_plugin->plugin, + "%s could not substract HTLC amounts, " + "half total htlc amount = %s, " + "flow->amounts[%lld] = %s.", + __PRETTY_FUNCTION__, + type_to_string(tmpctx, struct amount_msat, &h->htlc_total), + i, + type_to_string(tmpctx, struct amount_msat, &flow->amounts[i])); + } + if (h->num_htlcs == 0) + { + plugin_err(pay_plugin->plugin, + "%s could not decrease HTLC count.", + __PRETTY_FUNCTION__); + } + h->num_htlcs--; + } +} +void commit_htlc_payflow( + struct chan_extra_map *chan_extra_map, + const struct pay_flow *flow) +{ + for (size_t i = 0; i < tal_count(flow->path_scids); i++) { + struct chan_extra_half *h = get_chan_extra_half_by_scid( + chan_extra_map, + flow->path_scids[i], + flow->path_dirs[i]); + if(!h) + { + plugin_err(pay_plugin->plugin, + "%s could not resolve chan_extra_half", + __PRETTY_FUNCTION__); + } + if (!amount_msat_add(&h->htlc_total, h->htlc_total, flow->amounts[i])) + { + plugin_err(pay_plugin->plugin, + "%s could not add HTLC amounts, " + "flow->amounts[%lld] = %s.", + __PRETTY_FUNCTION__, + i, + type_to_string(tmpctx, struct amount_msat, &flow->amounts[i])); + } + h->num_htlcs++; + } +} + +/* How much does this flow deliver to destination? */ +struct amount_msat payflow_delivered(const struct pay_flow *flow) +{ + return flow->amounts[tal_count(flow->amounts)-1]; +} + +struct pay_flow* payflow_fail(struct pay_flow *flow) +{ + debug_assert(flow); + struct payment * p = flow->payment; + debug_assert(p); + + payment_fail(p); + amount_msat_reduce(&p->total_delivering, payflow_delivered(flow)); + amount_msat_reduce(&p->total_sent, flow->amounts[0]); + + /* Release the HTLCs in the uncertainty_network. */ + return tal_free(flow); +} + + diff --git a/plugins/renepay/pay_flow.h b/plugins/renepay/pay_flow.h index faabc770461f..0cf58e0bd712 100644 --- a/plugins/renepay/pay_flow.h +++ b/plugins/renepay/pay_flow.h @@ -1,15 +1,30 @@ #ifndef LIGHTNING_PLUGINS_RENEPAY_PAY_FLOW_H #define LIGHTNING_PLUGINS_RENEPAY_PAY_FLOW_H #include +#include +#include +#include +#include +#include /* This is like a struct flow, but independent of gossmap, and contains * all we need to actually send the part payment. */ struct pay_flow { /* So we can be an independent object for callbacks. */ - struct payment *payment; + struct payment * payment; + + // TODO(eduardo): remove this, unnecessary + int attempt; - /* Part id for RPC interface */ - u64 partid; + /* Information to link this flow to a unique sendpay. */ + struct payflow_key + { + // TODO(eduardo): pointer or value? + struct sha256 *payment_hash; + u64 groupid; + u64 partid; + } key; + /* The series of channels and nodes to traverse. */ struct short_channel_id *path_scids; struct node_id *path_nodes; @@ -22,10 +37,75 @@ struct pay_flow { double success_prob; }; -struct pay_flow **get_payflows(struct payment *p, +static inline struct payflow_key +payflow_key(struct sha256 *hash, u64 groupid, u64 partid) +{ + struct payflow_key k= {hash,groupid,partid}; + return k; +} + +static inline const char* fmt_payflow_key( + const tal_t *ctx, + const struct payflow_key * k) +{ + char *str = tal_fmt( + ctx, + "key: groupid=%ld, partid=%ld, payment_hash=%s", + k->groupid,k->partid, + type_to_string(ctx,struct sha256,k->payment_hash)); + return str; +} + + +static inline const struct payflow_key +payflow_get_key(const struct pay_flow * pf) +{ + return pf->key; +} + +static inline size_t payflow_key_hash(const struct payflow_key k) +{ + return k.payment_hash->u.u32[0] ^ (k.groupid << 32) ^ k.partid; +} + +static inline bool payflow_key_equal(struct pay_flow const *pf, + const struct payflow_key k) +{ + return pf->key.partid==k.partid && pf->key.groupid==k.groupid + && sha256_eq(pf->key.payment_hash,k.payment_hash); +} + +HTABLE_DEFINE_TYPE(struct pay_flow, + payflow_get_key, payflow_key_hash, payflow_key_equal, + payflow_map); + + +struct pay_flow **get_payflows(struct renepay * renepay, struct amount_msat amount, struct amount_msat feebudget, bool unlikely_ok, - bool is_entire_payment); + bool is_entire_payment, + char const ** err_msg); + +void commit_htlc_payflow( + struct chan_extra_map *chan_extra_map, + const struct pay_flow *flow); + +void remove_htlc_payflow( + struct chan_extra_map *chan_extra_map, + struct pay_flow *flow); + +const char *flow_path_to_str(const tal_t *ctx, const struct pay_flow *flow); + +const char* fmt_payflows(const tal_t *ctx, + struct pay_flow ** flows); + +/* How much does this flow deliver to destination? */ +struct amount_msat payflow_delivered(const struct pay_flow *flow); + +/* Removes amounts from payment and frees flow pointer. + * A possible destructor for flow would remove HTLCs from the + * uncertainty_network and remove the flow from any data structure. */ +struct pay_flow* payflow_fail(struct pay_flow *flow); #endif /* LIGHTNING_PLUGINS_RENEPAY_PAY_FLOW_H */ diff --git a/plugins/renepay/payment.c b/plugins/renepay/payment.c new file mode 100644 index 000000000000..832d1015ef04 --- /dev/null +++ b/plugins/renepay/payment.c @@ -0,0 +1,229 @@ +#include +#include +#include + +struct payment * payment_new(struct renepay * renepay) +{ + struct payment *p = tal(renepay,struct payment); + p->renepay = renepay; + p->paynotes = tal_arr(p, const char *, 0); + + p->total_sent = AMOUNT_MSAT(0); + p->total_delivering = AMOUNT_MSAT(0); + + p->invstr=NULL; + + p->amount = AMOUNT_MSAT(0); + // p->destination= + // p->payment_hash + p->maxspend = AMOUNT_MSAT(0); + p->maxdelay=0; + // p->start_time= + // p->stop_time= + p->preimage = NULL; + p->payment_secret=NULL; + p->payment_metadata=NULL; + p->status=PAYMENT_PENDING; + p->final_cltv=0; + // p->list= + p->description=NULL; + p->label=NULL; + + p->delay_feefactor=0; + p->base_fee_penalty=0; + p->prob_cost_factor=0; + p->min_prob_success=0; + + p->local_offer_id=NULL; + p->use_shadow=true; + p->groupid=1; + + p->result = NULL; + return p; +} + +struct renepay * renepay_new(struct command *cmd) +{ + struct renepay *renepay = tal(cmd,struct renepay); + + renepay->cmd = cmd; + renepay->payment = payment_new(renepay); + renepay->localmods_applied=false; + renepay->local_gossmods = gossmap_localmods_new(renepay); + renepay->disabled = tal_arr(renepay,struct short_channel_id,0); + renepay->rexmit_timer = NULL; + renepay->next_attempt=1; + renepay->next_partid=1; + renepay->all_flows = tal(renepay,tal_t); + + return renepay; +} + + +void payment_fail(struct payment * p) +{ + /* If the payment already succeeded this function call must correspond + * to an old sendpay. */ + if(p->status == PAYMENT_SUCCESS)return; + p->status=PAYMENT_FAIL; +} +void payment_success(struct payment * p) +{ + p->status=PAYMENT_SUCCESS; +} + +struct amount_msat payment_sent(struct payment const * p) +{ + return p->total_sent; +} +struct amount_msat payment_delivered(struct payment const * p) +{ + return p->total_delivering; +} +struct amount_msat payment_amount(struct payment const * p) +{ + return p->amount; +} + +struct amount_msat payment_fees(struct payment const*p) +{ + struct amount_msat fees; + struct amount_msat sent = payment_sent(p), + delivered = payment_delivered(p); + + if(!amount_msat_sub(&fees,sent,delivered)) + debug_err( "Strange, sent amount (%s) is less than delivered (%s), aborting.", + type_to_string(tmpctx,struct amount_msat,&sent), + type_to_string(tmpctx,struct amount_msat,&delivered)); + return fees; +} + +void payment_note(struct payment *p, const char *fmt, ...) +{ + va_list ap; + const char *str; + + va_start(ap, fmt); + str = tal_vfmt(p->paynotes, fmt, ap); + va_end(ap); + tal_arr_expand(&p->paynotes, str); + debug_info("%s",str); +} + +void payment_assert_delivering_incomplete(struct payment const * p) +{ + if(!amount_msat_less(p->total_delivering, p->amount)) + { + debug_err( + "Strange, delivering (%s) is not smaller than amount (%s)", + type_to_string(tmpctx,struct amount_msat,&p->total_delivering), + type_to_string(tmpctx,struct amount_msat,&p->amount)); + } +} +void payment_assert_delivering_all(struct payment const * p) +{ + if(amount_msat_less(p->total_delivering, p->amount)) + { + debug_err( + "Strange, delivering (%s) is less than amount (%s)", + type_to_string(tmpctx,struct amount_msat,&p->total_delivering), + type_to_string(tmpctx,struct amount_msat,&p->amount)); + } +} + + +int renepay_current_attempt(const struct renepay * renepay) +{ + return renepay->next_attempt-1; +} +int renepay_attempt_count(const struct renepay * renepay) +{ + return renepay->next_attempt-1; +} +void renepay_new_attempt(struct renepay * renepay) +{ + renepay->payment->status=PAYMENT_PENDING; + renepay->next_attempt++; +} +struct command_result *renepay_success(struct renepay * renepay) +{ + debug_info("calling %s",__PRETTY_FUNCTION__); + struct payment *p = renepay->payment; + + payment_success(p); + payment_assert_delivering_all(p); + + struct json_stream *response + = jsonrpc_stream_success(renepay->cmd); + + /* Any one succeeding is success. */ + json_add_preimage(response, "payment_preimage", p->preimage); + json_add_sha256(response, "payment_hash", &p->payment_hash); + json_add_timeabs(response, "created_at", p->start_time); + json_add_u32(response, "parts", renepay_parts(renepay)); + json_add_amount_msat_only(response, "amount_msat", + p->amount); + json_add_amount_msat_only(response, "amount_sent_msat", + p->total_sent); + json_add_string(response, "status", "complete"); + json_add_node_id(response, "destination", &p->destination); + + return command_finished(renepay->cmd, response); +} + +struct command_result *renepay_fail( + struct renepay * renepay, + enum jsonrpc_errcode code, + const char *fmt, ...) +{ + /* renepay_fail is called after command finished. */ + if(renepay==NULL) + { + return command_still_pending(NULL); + } + payment_fail(renepay->payment); + + va_list args; + va_start(args, fmt); + char *message = tal_vfmt(tmpctx,fmt,args); + va_end(args); + + debug_paynote(renepay->payment,"%s",message); + + return command_fail(renepay->cmd,code,"%s",message); +} + +u64 renepay_parts(struct renepay const * renepay) +{ + return renepay->next_partid-1; +} + +/* Either the payment succeeded or failed, we need to cleanup/set the plugin + * into a valid state before the next payment. */ +void renepay_cleanup( + struct renepay * renepay, + struct gossmap * gossmap) +{ + debug_info("calling %s",__PRETTY_FUNCTION__); + /* Always remove our local mods (routehints) so others can use + * gossmap. We do this only after the payment completes. */ + // TODO(eduardo): it can happen that local_gossmods removed below + // contained a set of channels for which there is information in the + // uncertainty network (chan_extra_map) and that are part of some pending + // payflow (payflow_map). Handle this situation. + if(renepay->localmods_applied) + gossmap_remove_localmods(gossmap, + renepay->local_gossmods); + // TODO(eduardo): I wonder if it is possible to have two instances of + // renepay at the same time. + // 1st problem: dijkstra datastructure is global, this can be fixed, + // 2nd problem: we don't know if gossmap_apply_localmods and gossmap_remove_localmods, + // can handle different local_gossmods applied to the same gossmap. + renepay->localmods_applied=false; + tal_free(renepay->local_gossmods); + + renepay->rexmit_timer = tal_free(renepay->rexmit_timer); + + if(renepay->payment) + renepay->payment->renepay = NULL; +} diff --git a/plugins/renepay/payment.h b/plugins/renepay/payment.h new file mode 100644 index 000000000000..d46ddc7b9c4a --- /dev/null +++ b/plugins/renepay/payment.h @@ -0,0 +1,163 @@ +#ifndef LIGHTNING_PLUGINS_RENEPAY_PAYMENT_H +#define LIGHTNING_PLUGINS_RENEPAY_PAYMENT_H +#include "config.h" +#include +#include + +enum payment_status { + PAYMENT_PENDING, PAYMENT_SUCCESS, PAYMENT_FAIL +}; + + +struct payment { + struct renepay * renepay; + + /* Chatty description of attempts. */ + const char **paynotes; + + /* Total sent, including fees. */ + struct amount_msat total_sent; + + /* Total that is delivering (i.e. without fees) */ + struct amount_msat total_delivering; + + /* invstring (bolt11 or bolt12) */ + const char *invstr; + + /* How much, what, where */ + struct amount_msat amount; + struct node_id destination; + struct sha256 payment_hash; + + + /* Limits on what routes we'll accept. */ + struct amount_msat maxspend; + + /* Max accepted HTLC delay.*/ + unsigned int maxdelay; + + /* We promised this in pay() output */ + struct timeabs start_time; + + /* We stop trying after this time is reached. */ + struct timeabs stop_time; + + /* Payment preimage, in case of success. */ + const struct preimage *preimage; + + /* payment_secret, if specified by invoice. */ + struct secret *payment_secret; + + /* Payment metadata, if specified by invoice. */ + const u8 *payment_metadata; + + /* To know if the last attempt failed, succeeded or is it pending. */ + enum payment_status status; + + u32 final_cltv; + + /* Inside pay_plugin->payments list */ + struct list_node list; + + /* Description and labels, if any. */ + const char *description, *label; + + + /* Penalty for CLTV delays */ + double delay_feefactor; + + /* Penalty for base fee */ + double base_fee_penalty; + + /* With these the effective linear fee cost is computed as + * + * linear fee cost = + * millionths + * + base_fee* base_fee_penalty + * +delay*delay_feefactor; + * */ + + /* The minimum acceptable prob. of success */ + double min_prob_success; + + /* Conversion from prob. cost to millionths */ + double prob_cost_factor; + /* linear prob. cost = + * - prob_cost_factor * log prob. */ + + + /* If this is paying a local offer, this is the one (sendpay ensures we + * don't pay twice for single-use offers) */ + // TODO(eduardo): this is not being used! + struct sha256 *local_offer_id; + + /* DEVELOPER allows disabling shadow route */ + bool use_shadow; + + /* Groupid, so listpays() can group them back together */ + u64 groupid; + + struct command_result * result; +}; + +/* Data only kept while the payment is being processed. */ +struct renepay +{ + /* The command, and our owner (needed for timer func) */ + struct command *cmd; + + /* Payment information that will eventually outlive renepay and be + * registered. */ + struct payment * payment; + + /* Localmods to apply to gossip_map for our own use. */ + bool localmods_applied; + struct gossmap_localmods *local_gossmods; + + /* Channels we decided to disable for various reasons. */ + struct short_channel_id *disabled; + + /* Timers. */ + struct plugin_timer *rexmit_timer; + + /* Keep track of the number of attempts. */ + int next_attempt; + /* Used in get_payflows to set ids to each pay_flow. */ + u64 next_partid; + + /* Root to destroy pending flows */ + tal_t *all_flows; +}; + +struct payment * payment_new(struct renepay *renepay); +struct renepay * renepay_new(struct command *cmd); +void renepay_cleanup( + struct renepay * renepay, + struct gossmap * gossmap); + +void payment_fail(struct payment * p); +void payment_success(struct payment * p); +struct amount_msat payment_sent(struct payment const * p); +struct amount_msat payment_delivered(struct payment const * p); +struct amount_msat payment_amount(struct payment const * p); +struct amount_msat payment_fees(struct payment const*p); + +void payment_note(struct payment *p, const char *fmt, ...); +void payment_assert_delivering_incomplete(struct payment const * p); +void payment_assert_delivering_all(struct payment const * p); + + +int renepay_current_attempt(const struct renepay *renepay); +int renepay_attempt_count(const struct renepay *renepay); +void renepay_new_attempt(struct renepay *renepay); + +struct command_result *renepay_success(struct renepay *renepay); + +struct command_result *renepay_fail( + struct renepay * renepay, + enum jsonrpc_errcode code, + const char *fmt, ...); + +u64 renepay_parts(struct renepay const * renepay); + +#endif // LIGHTNING_PLUGINS_RENEPAY_PAYMENT_H diff --git a/plugins/renepay/test/Makefile b/plugins/renepay/test/Makefile index 79c7916bd475..4cf2bfa63349 100644 --- a/plugins/renepay/test/Makefile +++ b/plugins/renepay/test/Makefile @@ -9,17 +9,9 @@ ALL_TEST_PROGRAMS += $(PLUGIN_RENEPAY_TEST_PROGRAMS) $(PLUGIN_RENEPAY_TEST_OBJS): $(PLUGIN_RENEPAY_SRC) PLUGIN_RENEPAY_TEST_COMMON_OBJS := \ - common/amount.o \ - common/autodata.o \ - common/fp16.o \ - common/gossmap.o \ - common/node_id.o \ - common/pseudorand.o \ - common/dijkstra.o \ - common/setup.o \ - common/type_to_string.o \ - common/utils.o + plugins/renepay/dijkstra.o \ + plugins/renepay/debug.o -$(PLUGIN_RENEPAY_TEST_PROGRAMS): $(CCAN_OBJS) $(BITCOIN_OBJS) $(WIRE_OBJS) $(PLUGIN_RENEPAY_TEST_COMMON_OBJS) +$(PLUGIN_RENEPAY_TEST_PROGRAMS): $(PLUGIN_RENEPAY_TEST_COMMON_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) $(CCAN_OBJS) bitcoin/chainparams.o common/gossmap.o common/fp16.o common/dijkstra.o common/bolt12.o common/bolt12_merkle.o wire/bolt12$(EXP)_wiregen.o -check-units: $(PLUGIN_RENEPAY_TEST_PROGRAMS:%=unittest/%) +check-renepay: $(PLUGIN_RENEPAY_TEST_PROGRAMS:%=unittest/%) diff --git a/plugins/renepay/test/run-dijkstra.c b/plugins/renepay/test/run-dijkstra.c new file mode 100644 index 000000000000..7e9d208270ef --- /dev/null +++ b/plugins/renepay/test/run-dijkstra.c @@ -0,0 +1,100 @@ +#include "config.h" +#include +#include +#include +#include +#include +#include +#include + +#include + +static void insertion_in_increasing_distance(const tal_t *ctx) +{ + dijkstra_malloc(ctx,10); + + for(int i=0;i +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 = flow_set_probability(flows,gossmap,chan_extra_map); + 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 ? "->" : "", + type_to_string(this_ctx, struct short_channel_id, &scid)); + } + delivered = flows[i]->amounts[tal_count(flows[i]->amounts)-1]; + if (!amount_msat_sub(&fee, flows[i]->amounts[0], delivered)) + { + debug_err("%s: flow[i]->amount[0]success_prob, + type_to_string(this_ctx, struct amount_msat, &delivered), + type_to_string(this_ctx, struct amount_msat, &fee)); + } + + tal_free(this_ctx); + return buff; +} + +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; + + common_setup(argv[0]); + + fd = tmpdir_mkstemp(tmpctx, "run-not_mcf-diamond.XXXXXX", &gossfile); + 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", 7, &scid12)); + assert(short_channel_id_from_str("1x3x0", 7, &scid13)); + assert(short_channel_id_from_str("2x4x0", 7, &scid24)); + assert(short_channel_id_from_str("3x4x0", 7, &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)); + + struct flow **flows; + flows = minflow(tmpctx, gossmap, + gossmap_find_node(gossmap, &l1), + gossmap_find_node(gossmap, &l4), + chan_extra_map, NULL, + /* Half the capacity */ + AMOUNT_MSAT(1000000), // 1000 sats + /* max_fee = */ AMOUNT_MSAT(10000), // 10 sats + /* min probability = */ 0.8, // 80% + /* delay fee factor = */ 0, + /* base fee penalty */ 0, + /* prob cost factor = */ 1); + + debug_info("%s\n", + print_flows(tmpctx,"Simple minflow", gossmap,chan_extra_map, flows)); + + common_shutdown(); +} diff --git a/plugins/renepay/test/run-not_mcf.c b/plugins/renepay/test/run-mcf.c similarity index 77% rename from plugins/renepay/test/run-not_mcf.c rename to plugins/renepay/test/run-mcf.c index 4764dac2035b..c2c994817e44 100644 --- a/plugins/renepay/test/run-not_mcf.c +++ b/plugins/renepay/test/run-mcf.c @@ -1,6 +1,11 @@ #include "config.h" + +#define RENEPAY_UNITTEST // logs are written in /tmp/debug.txt +#include "../payment.c" #include "../flow.c" -#include "../not_mcf.c" +#include "../uncertainty_network.c" +#include "../mcf.c" + #include #include #include @@ -12,28 +17,6 @@ #include #include -/* AUTOGENERATED MOCKS START */ -/* Generated stub for fromwire_bigsize */ -bigsize_t fromwire_bigsize(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) -{ fprintf(stderr, "fromwire_bigsize called!\n"); abort(); } -/* Generated stub for fromwire_channel_id */ -bool fromwire_channel_id(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, - struct channel_id *channel_id UNNEEDED) -{ fprintf(stderr, "fromwire_channel_id called!\n"); abort(); } -/* Generated stub for fromwire_wireaddr */ -bool fromwire_wireaddr(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, struct wireaddr *addr UNNEEDED) -{ fprintf(stderr, "fromwire_wireaddr called!\n"); abort(); } -/* Generated stub for towire_bigsize */ -void towire_bigsize(u8 **pptr UNNEEDED, const bigsize_t val UNNEEDED) -{ fprintf(stderr, "towire_bigsize called!\n"); abort(); } -/* Generated stub for towire_channel_id */ -void towire_channel_id(u8 **pptr UNNEEDED, const struct channel_id *channel_id UNNEEDED) -{ fprintf(stderr, "towire_channel_id called!\n"); abort(); } -/* Generated stub for towire_wireaddr */ -void towire_wireaddr(u8 **pptr UNNEEDED, const struct wireaddr *addr UNNEEDED) -{ fprintf(stderr, "towire_wireaddr called!\n"); abort(); } -/* AUTOGENERATED MOCKS END */ - /* Canned gossmap, taken from tests/test_gossip.py's * setup_gossip_store_test via od -v -Anone -tx1 < /tmp/ltests-kaf30pn0/test_gossip_store_compact_noappend_1/lightning-2/regtest/gossip_store */ @@ -247,29 +230,34 @@ static u8 canned_map[] = { /* not_mcf sets NDEBUG, so assert() is useless */ #define ASSERT(x) do { if (!(x)) abort(); } while(0) -static void print_flows(const char *desc, - const struct gossmap *gossmap, - struct flow **flows) +static const char *print_flows( + const tal_t *ctx, + const char *desc, + const struct gossmap *gossmap, + struct flow **flows) { - printf("%s: %zu subflows\n", desc, tal_count(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; - printf(" "); + 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]); - printf("%s%s", j ? "->" : "", - type_to_string(tmpctx, struct short_channel_id, &scid)); + tal_append_fmt(&buff,"%s%s", j ? "->" : "", + type_to_string(this_ctx, struct short_channel_id, &scid)); } delivered = flows[i]->amounts[tal_count(flows[i]->amounts)-1]; if (!amount_msat_sub(&fee, flows[i]->amounts[0], delivered)) abort(); - printf(" prob %.2f, %s delivered with fee %s\n", + tal_append_fmt(&buff," prob %.2f, %s delivered with fee %s\n", flows[i]->success_prob, - type_to_string(tmpctx, struct amount_msat, &delivered), - type_to_string(tmpctx, struct amount_msat, &fee)); + type_to_string(this_ctx, struct amount_msat, &delivered), + type_to_string(this_ctx, struct amount_msat, &fee)); } + tal_free(this_ctx); + return buff; } int main(int argc, char *argv[]) @@ -278,91 +266,101 @@ int main(int argc, char *argv[]) char *gossfile; struct gossmap *gossmap; struct node_id l1, l2, l3; - struct flow **flows, **flows2; - struct short_channel_id scid12, scid23, scid13; - struct gossmap_localmods *mods; + struct flow **flows; + struct short_channel_id scid12, scid23; struct chan_extra_map *chan_extra_map; - struct chan_extra *ce; - struct gossmap_chan *local_chan; common_setup(argv[0]); fd = tmpdir_mkstemp(tmpctx, "run-not_mcf.XXXXXX", &gossfile); - ASSERT(write_all(fd, canned_map, sizeof(canned_map))); + assert(write_all(fd, canned_map, sizeof(canned_map))); gossmap = gossmap_load(tmpctx, gossfile, NULL); - ASSERT(gossmap); + assert(gossmap); + remove(gossfile); /* There is a public channel 2<->3 (103x1x0), and private * 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("110x1x1", 7, &scid12)); - ASSERT(short_channel_id_from_str("103x1x0", 7, &scid23)); + 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("110x1x1", 7, &scid12)); + assert(short_channel_id_from_str("103x1x0", 7, &scid23)); chan_extra_map = tal(tmpctx, struct chan_extra_map); chan_extra_map_init(chan_extra_map); + uncertainty_network_update(gossmap,chan_extra_map); + flows = minflow(tmpctx, gossmap, gossmap_find_node(gossmap, &l1), gossmap_find_node(gossmap, &l3), chan_extra_map, NULL, /* Half the capacity */ AMOUNT_MSAT(500000000), - 1, - 1); - print_flows("Flow via single path l1->l2->l3", gossmap, flows); - + /* max_fee = */ AMOUNT_MSAT(1000000), // 1k sats + /* min probability = */ 0.1, + /* delay fee factor = */ 1, + /* base fee penalty */ 1, + /* prob cost factor = */ 10); + commit_flow_set(gossmap,chan_extra_map,flows); + debug_info("%s\n", + print_flows(tmpctx,"Flow via single path l1->l2->l3", gossmap, flows)); + + + /* Should go 1->2->3 */ - ASSERT(tal_count(flows) == 1); - 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); - ASSERT(amount_msat_eq(flows[0]->amounts[1], AMOUNT_MSAT(500000000))); + assert(tal_count(flows) == 1); + 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); + assert(amount_msat_eq(flows[0]->amounts[1], AMOUNT_MSAT(500000000))); /* fee_base_msat == 20, fee_proportional_millionths == 1000 */ - ASSERT(amount_msat_eq(flows[0]->amounts[0], AMOUNT_MSAT(500000000 + 500000 + 20))); + assert(amount_msat_eq(flows[0]->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.250); + assert(flows[0]->success_prob > 0.249); + assert(flows[0]->success_prob <= 0.250); + /* Should have filled in some extra data! */ - ce = chan_extra_map_get(chan_extra_map, scid12); - ASSERT(short_channel_id_eq(&ce->scid, &scid12)); + struct chan_extra *ce = chan_extra_map_get(chan_extra_map, 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))); + 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 = chan_extra_map_get(chan_extra_map, scid23); - ASSERT(short_channel_id_eq(&ce->scid, &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))); - - /* Now try adding a local channel scid */ - mods = gossmap_localmods_new(tmpctx); - ASSERT(short_channel_id_from_str("111x1x1", 7, &scid13)); + 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))); + + // /* 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, + assert(gossmap_local_addchan(mods, &l1, &l3, &scid13, NULL)); + assert(gossmap_local_updatechan(mods, &scid13, AMOUNT_MSAT(0), AMOUNT_MSAT(400000000), 0, 1000, 5, @@ -371,88 +369,102 @@ int main(int argc, char *argv[]) /* Apply changes, check they work. */ gossmap_apply_localmods(gossmap, mods); - local_chan = gossmap_find_chan(gossmap, &scid13); - ASSERT(local_chan); + struct gossmap_chan *local_chan = gossmap_find_chan(gossmap, &scid13); + assert(local_chan); /* Clear that */ - remove_completed_flow(gossmap, chan_extra_map, flows[0]); + remove_completed_flow_set(gossmap, chan_extra_map, flows); /* The local chans have no "capacity", so set it manually. */ - new_chan_extra_half(chan_extra_map, scid13, 0, + new_chan_extra(chan_extra_map, 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), - 0.1, - 1); - - print_flows("Flow via two paths, low mu", gossmap, flows); - - ASSERT(tal_count(flows) == 2); - 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]); + // 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]); /* Higher mu values mean we pay more for certainty! */ - flows2 = minflow(tmpctx, gossmap, + struct flow **flows2 = minflow(tmpctx, gossmap, gossmap_find_node(gossmap, &l1), gossmap_find_node(gossmap, &l3), chan_extra_map, NULL, /* This will go 400000000 via 1->3, rest via 1-2-3. */ - AMOUNT_MSAT(500000000), - 10, - 1); - print_flows("Flow via two paths, high mu", gossmap, flows2); - ASSERT(tal_count(flows2) == 2); - ASSERT(tal_count(flows2[0]->path) == 1); - ASSERT(tal_count(flows2[1]->path) == 2); - - /* Sends more via 1->3, since it's more expensive (but lower prob) */ - ASSERT(amount_msat_greater(flows2[0]->amounts[0], flows[1]->amounts[0])); - ASSERT(flows2[0]->success_prob < flows[1]->success_prob); + /* amount = */ AMOUNT_MSAT(500000000), //500k sats + /* max_fee = */ AMOUNT_MSAT(1000000), // 1k sats + /* min probability = */ 0.1, // 10% + /* delay fee factor = */ 1, + /* base fee penalty */ 1, + /* prob cost factor = */ 10); + debug_info("%s\n", + print_flows(tmpctx,"Flow via two paths, high mu", gossmap, flows2)); + assert(tal_count(flows2) == 2); + assert(tal_count(flows2[0]->path) == 1); + assert(tal_count(flows2[1]->path) == 2); + + // /* Sends more via 1->3, since it's more expensive (but lower prob) */ + assert(amount_msat_greater(flows2[0]->amounts[0], flows2[1]->amounts[0])); + assert(flows2[0]->success_prob < flows2[1]->success_prob); /* Delivered amount must be the total! */ - ASSERT(flows2[0]->amounts[0].millisatoshis + assert(flows2[0]->amounts[0].millisatoshis + flows2[1]->amounts[1].millisatoshis == 500000000); - /* But in total it's more expensive! */ - ASSERT(flows2[0]->amounts[0].millisatoshis + flows2[1]->amounts[0].millisatoshis - > flows[0]->amounts[0].millisatoshis - flows[1]->amounts[0].millisatoshis); - + // /* But in total it's more expensive! */ + assert(flows2[0]->amounts[0].millisatoshis + flows2[1]->amounts[0].millisatoshis + > flows2[0]->amounts[0].millisatoshis - flows2[1]->amounts[0].millisatoshis); common_shutdown(); } diff --git a/plugins/renepay/test/run-not_mcf-diamond.c b/plugins/renepay/test/run-not_mcf-diamond.c deleted file mode 100644 index 50ac603b33ce..000000000000 --- a/plugins/renepay/test/run-not_mcf-diamond.c +++ /dev/null @@ -1,157 +0,0 @@ -#include "config.h" -#include "../flow.c" -#include "../not_mcf.c" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* AUTOGENERATED MOCKS START */ -/* Generated stub for fromwire_bigsize */ -bigsize_t fromwire_bigsize(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) -{ fprintf(stderr, "fromwire_bigsize called!\n"); abort(); } -/* Generated stub for fromwire_channel_id */ -bool fromwire_channel_id(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, - struct channel_id *channel_id UNNEEDED) -{ fprintf(stderr, "fromwire_channel_id called!\n"); abort(); } -/* Generated stub for fromwire_wireaddr */ -bool fromwire_wireaddr(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, struct wireaddr *addr UNNEEDED) -{ fprintf(stderr, "fromwire_wireaddr called!\n"); abort(); } -/* Generated stub for towire_bigsize */ -void towire_bigsize(u8 **pptr UNNEEDED, const bigsize_t val UNNEEDED) -{ fprintf(stderr, "towire_bigsize called!\n"); abort(); } -/* Generated stub for towire_channel_id */ -void towire_channel_id(u8 **pptr UNNEEDED, const struct channel_id *channel_id UNNEEDED) -{ fprintf(stderr, "towire_channel_id called!\n"); abort(); } -/* Generated stub for towire_wireaddr */ -void towire_wireaddr(u8 **pptr UNNEEDED, const struct wireaddr *addr UNNEEDED) -{ fprintf(stderr, "towire_wireaddr called!\n"); abort(); } -/* AUTOGENERATED MOCKS END */ - -static u8 empty_map[] = { - 0 -}; - -static void print_flows(const char *desc, - const struct gossmap *gossmap, - struct flow **flows) -{ - printf("%s: %zu subflows\n", desc, tal_count(flows)); - for (size_t i = 0; i < tal_count(flows); i++) { - struct amount_msat fee, delivered; - printf(" "); - 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]); - printf("%s%s", j ? "->" : "", - type_to_string(tmpctx, struct short_channel_id, &scid)); - } - delivered = flows[i]->amounts[tal_count(flows[i]->amounts)-1]; - if (!amount_msat_sub(&fee, flows[i]->amounts[0], delivered)) - abort(); - printf(" prob %.2f, %s delivered with fee %s\n", - flows[i]->success_prob, - type_to_string(tmpctx, struct amount_msat, &delivered), - type_to_string(tmpctx, struct amount_msat, &fee)); - } -} - -int main(int argc, char *argv[]) -{ - int fd; - char *gossfile; - struct gossmap *gossmap; - struct node_id l1, l2, l3, l4; - struct flow **flows; - struct short_channel_id scid12, scid13, scid24, scid34; - struct gossmap_localmods *mods; - struct chan_extra_map *chan_extra_map; - - common_setup(argv[0]); - - fd = tmpdir_mkstemp(tmpctx, "run-not_mcf-diamond.XXXXXX", &gossfile); - 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", 7, &scid12)); - ASSERT(short_channel_id_from_str("1x3x0", 7, &scid13)); - ASSERT(short_channel_id_from_str("2x4x0", 7, &scid24)); - ASSERT(short_channel_id_from_str("3x4x0", 7, &scid34)); - - mods = gossmap_localmods_new(tmpctx); - - /* 1->2->4 has capacity 10M sat, 1->3->4 has capacity 5M sat (lower fee!) */ - ASSERT(gossmap_local_addchan(mods, &l1, &l2, &scid12, NULL)); - ASSERT(gossmap_local_updatechan(mods, &scid12, - AMOUNT_MSAT(0), - AMOUNT_MSAT(1000000000), - 0, 1000, 5, - true, - 0)); - ASSERT(gossmap_local_addchan(mods, &l2, &l4, &scid24, NULL)); - ASSERT(gossmap_local_updatechan(mods, &scid24, - AMOUNT_MSAT(0), - AMOUNT_MSAT(1000000000), - 0, 1000, 5, - true, - 0)); - ASSERT(gossmap_local_addchan(mods, &l1, &l3, &scid13, NULL)); - ASSERT(gossmap_local_updatechan(mods, &scid13, - AMOUNT_MSAT(0), - AMOUNT_MSAT(500000000), - 0, 500, 5, - true, - 0)); - ASSERT(gossmap_local_addchan(mods, &l3, &l4, &scid34, NULL)); - ASSERT(gossmap_local_updatechan(mods, &scid34, - AMOUNT_MSAT(0), - AMOUNT_MSAT(500000000), - 0, 500, 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_half(chan_extra_map, - scid12, 0, - AMOUNT_MSAT(1000000000)); - new_chan_extra_half(chan_extra_map, - scid24, 0, - AMOUNT_MSAT(1000000000)); - new_chan_extra_half(chan_extra_map, - scid13, 0, - AMOUNT_MSAT(500000000)); - new_chan_extra_half(chan_extra_map, - scid34, 0, - AMOUNT_MSAT(500000000)); - - flows = minflow(tmpctx, gossmap, - gossmap_find_node(gossmap, &l1), - gossmap_find_node(gossmap, &l4), - chan_extra_map, NULL, - /* Half the capacity */ - AMOUNT_MSAT(500000000), - 0.2, - 1); - - print_flows("Simple minflow", gossmap, flows); - ASSERT(tal_count(flows) == 2); - - common_shutdown(); -} diff --git a/plugins/renepay/test/run-not_mcf-gossmap.c b/plugins/renepay/test/run-not_mcf-gossmap.c deleted file mode 100644 index e69f947b99b2..000000000000 --- a/plugins/renepay/test/run-not_mcf-gossmap.c +++ /dev/null @@ -1,128 +0,0 @@ -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -//static bool print_enable = true; -//#define SUPERVERBOSE(...) do { if (print_enable) printf(__VA_ARGS__); } while(0) - #include "../not_mcf.c" - #include "../flow.c" - -/* AUTOGENERATED MOCKS START */ -/* Generated stub for fromwire_bigsize */ -bigsize_t fromwire_bigsize(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) -{ fprintf(stderr, "fromwire_bigsize called!\n"); abort(); } -/* Generated stub for fromwire_channel_id */ -bool fromwire_channel_id(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, - struct channel_id *channel_id UNNEEDED) -{ fprintf(stderr, "fromwire_channel_id called!\n"); abort(); } -/* Generated stub for fromwire_wireaddr */ -bool fromwire_wireaddr(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, struct wireaddr *addr UNNEEDED) -{ fprintf(stderr, "fromwire_wireaddr called!\n"); abort(); } -/* Generated stub for towire_bigsize */ -void towire_bigsize(u8 **pptr UNNEEDED, const bigsize_t val UNNEEDED) -{ fprintf(stderr, "towire_bigsize called!\n"); abort(); } -/* Generated stub for towire_channel_id */ -void towire_channel_id(u8 **pptr UNNEEDED, const struct channel_id *channel_id UNNEEDED) -{ fprintf(stderr, "towire_channel_id called!\n"); abort(); } -/* Generated stub for towire_wireaddr */ -void towire_wireaddr(u8 **pptr UNNEEDED, const struct wireaddr *addr UNNEEDED) -{ fprintf(stderr, "towire_wireaddr called!\n"); abort(); } -/* AUTOGENERATED MOCKS END */ - -/* not_mcf sets NDEBUG, so assert() is useless */ -#define ASSERT(x) do { if (!(x)) abort(); } while(0) - -static void print_flows(const char *desc, - const struct gossmap *gossmap, - struct flow **flows) -{ - struct amount_msat total_fee = AMOUNT_MSAT(0), - total_delivered = AMOUNT_MSAT(0); - - printf("%s: %zu subflows\n", desc, tal_count(flows)); - for (size_t i = 0; i < tal_count(flows); i++) { - struct amount_msat fee, delivered; - printf(" "); - 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]); - printf("%s%s", j ? "->" : "", - type_to_string(tmpctx, struct short_channel_id, &scid)); - } - delivered = flows[i]->amounts[tal_count(flows[i]->amounts)-1]; - if (!amount_msat_sub(&fee, flows[i]->amounts[0], delivered)) - abort(); - printf(" prob %.2f, %s delivered with fee %s\n", - flows[i]->success_prob, - type_to_string(tmpctx, struct amount_msat, &delivered), - type_to_string(tmpctx, struct amount_msat, &fee)); - if (!amount_msat_add(&total_fee, total_fee, fee)) - abort(); - if (!amount_msat_add(&total_delivered, total_delivered, delivered)) - abort(); - } - printf("Delivered %s at fee %s\n", - type_to_string(tmpctx, struct amount_msat, &total_delivered), - type_to_string(tmpctx, struct amount_msat, &total_fee)); -} - -int main(int argc, char *argv[]) -{ - struct gossmap *gossmap; - struct flow **flows; - struct node_id srcid, dstid; - struct gossmap_node *src, *dst; - struct amount_msat amount; - struct chan_extra_map *chan_extra_map; - bool verbose = false; - - common_setup(argv[0]); - opt_register_noarg("-v|--verbose", opt_set_bool, &verbose, - "Increase verbosity"); - - opt_parse(&argc, argv, opt_log_stderr_exit); - - if (argc != 6) - errx(1, "Usage: %s ", argv[0]); - - gossmap = gossmap_load(tmpctx, argv[1], NULL); - assert(gossmap); - - if (!node_id_from_hexstr(argv[2], strlen(argv[2]), &srcid) - || !node_id_from_hexstr(argv[3], strlen(argv[3]), &dstid)) - errx(1, "Usage: %s ", argv[0]); - - src = gossmap_find_node(gossmap, &srcid); - assert(src); - dst = gossmap_find_node(gossmap, &dstid); - assert(dst); - assert(src != dst); - - amount = amount_msat(atol(argv[4])); - assert(!amount_msat_eq(amount, AMOUNT_MSAT(0))); - - chan_extra_map = tal(tmpctx, struct chan_extra_map); - chan_extra_map_init(chan_extra_map); - - flows = minflow(tmpctx, gossmap, src, dst, - chan_extra_map, NULL, - amount, - atof(argv[5]), - 1); - - print_flows("Flows", gossmap, flows); - common_shutdown(); -} diff --git a/plugins/renepay/test/run-payflow_map.c b/plugins/renepay/test/run-payflow_map.c new file mode 100644 index 000000000000..c0a65904d55a --- /dev/null +++ b/plugins/renepay/test/run-payflow_map.c @@ -0,0 +1,96 @@ +/* Eduardo: testing payflow_map. + * */ + +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define RENEPAY_UNITTEST +#include + +static void destroy_payflow( + struct pay_flow *p, + struct payflow_map * map) +{ + printf("calling %s with %s\n", + __PRETTY_FUNCTION__, + fmt_payflow_key(tmpctx,&p->key)); + payflow_map_del(map, p); +} +static struct pay_flow* new_payflow( + const tal_t *ctx, + struct sha256 * payment_hash, + u64 gid, + u64 pid) +{ + struct pay_flow *p = tal(ctx,struct pay_flow); + + p->payment=NULL; + p->key.payment_hash=payment_hash; + p->key.groupid = gid; + p->key.partid = pid; + + return p; +} + +static void valgrind_ok1(void) +{ + const char seed[] = "seed"; + struct sha256 hash; + + sha256(&hash,seed,sizeof(seed)); + + tal_t *this_ctx = tal(tmpctx,tal_t); + + struct payflow_map *map + = tal(this_ctx, struct payflow_map); + + payflow_map_init(map); + + { + tal_t *local_ctx = tal(this_ctx,tal_t); + + struct pay_flow *p1 = new_payflow(local_ctx, + &hash,1,1); + struct pay_flow *p2 = new_payflow(local_ctx, + &hash,2,3); + + printf("key1 = %s\n",fmt_payflow_key(local_ctx,&p1->key)); + printf("key1 = %s\n",fmt_payflow_key(local_ctx,&p2->key)); + printf("key hash 1 = %ld\n",payflow_key_hash(p1->key)); + printf("key hash 2 = %ld\n",payflow_key_hash(p2->key)); + + payflow_map_add(map,p1); tal_add_destructor2(p1,destroy_payflow,map); + payflow_map_add(map,p2); tal_add_destructor2(p2,destroy_payflow,map); + + struct pay_flow *q1 = payflow_map_get(map,payflow_key(&hash,1,1)); + struct pay_flow *q2 = payflow_map_get(map,payflow_key(&hash,2,3)); + + assert(payflow_key_hash(q1->key)==payflow_key_hash(p1->key)); + assert(payflow_key_hash(q2->key)==payflow_key_hash(p2->key)); + + tal_free(local_ctx); + } + + tal_free(this_ctx); + +} +int main(int argc, char *argv[]) +{ + common_setup(argv[0]); + valgrind_ok1(); + common_shutdown(); +} + diff --git a/plugins/renepay/test/run-testflow.c b/plugins/renepay/test/run-testflow.c new file mode 100644 index 000000000000..32931db02124 --- /dev/null +++ b/plugins/renepay/test/run-testflow.c @@ -0,0 +1,696 @@ +#include "config.h" +#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 "../uncertainty_network.c" +#include "../mcf.c" + +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_complete(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); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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 = tal(this_ctx,struct flow); + struct amount_msat deliver; + + // flow 1->2 + F->path = tal_arr(F,struct gossmap_chan const *,1); + F->dirs = tal_arr(F,int,1); + F->path[0]=gossmap_find_chan(gossmap,&scid12); + F->dirs[0]=0; + deliver = AMOUNT_MSAT(250000000); + flow_complete(F,gossmap,chan_extra_map,deliver); + assert(amount_msat_eq(F->amounts[0],deliver)); + assert(fabs(F->success_prob - 0.5)4->5 + F->path = tal_arr(F,struct gossmap_chan const *,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); + flow_complete(F,gossmap,chan_extra_map,deliver); + assert(amount_msat_eq(F->amounts[0],amount_msat(250050016))); + assert(fabs(F->success_prob - 1.)3->4->5 + F->path = tal_arr(F,struct gossmap_chan const *,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); + flow_complete(F,gossmap,chan_extra_map,deliver); + assert(amount_msat_eq(F->amounts[0],amount_msat(250087534))); + assert(fabs(F->success_prob - 1. + 250.087534/2000)2->3->4->5 + F->path = tal_arr(F,struct gossmap_chan const *,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); + flow_complete(F,gossmap,chan_extra_map,deliver); + assert(amount_msat_eq(F->amounts[0],amount_msat(250112544))); + assert(fabs(F->success_prob - 0.43728117) +#include +#include +#include + +static bool chan_extra_check_invariants(struct chan_extra *ce) +{ + bool all_ok = true; + for(int i=0;i<2;++i) + { + all_ok &= amount_msat_less_eq(ce->half[i].known_min, + ce->half[i].known_max); + all_ok &= amount_msat_less_eq(ce->half[i].known_max, + ce->capacity); + } + struct amount_msat diff_cb,diff_ca; + + all_ok &= amount_msat_sub(&diff_cb,ce->capacity,ce->half[1].known_max); + all_ok &= amount_msat_sub(&diff_ca,ce->capacity,ce->half[1].known_min); + + all_ok &= amount_msat_eq(ce->half[0].known_min,diff_cb); + all_ok &= amount_msat_eq(ce->half[0].known_max,diff_ca); + return all_ok; +} + + +/* Checks the entire uncertainty network for invariant violations. */ +bool uncertainty_network_check_invariants(struct chan_extra_map *chan_extra_map) +{ + bool all_ok = true; + + struct chan_extra_map_iter it; + for(struct chan_extra *ce = chan_extra_map_first(chan_extra_map,&it); + ce && all_ok; + ce=chan_extra_map_next(chan_extra_map,&it)) + { + all_ok &= chan_extra_check_invariants(ce); + } + + return all_ok; +} + +static void add_hintchan( + struct chan_extra_map *chan_extra_map, + struct renepay * renepay, + 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) +{ + int dir = node_id_cmp(src, dst) < 0 ? 0 : 1; + + struct chan_extra *ce = chan_extra_map_get(chan_extra_map, + scid); + if(!ce) + { + /* this channel is not public, we don't know his capacity */ + // TODO(eduardo): one possible solution is set the capacity to + // MAX_CAP and the state to [0,MAX_CAP]. Alternatively we set + // the capacity to amoung and state to [amount,amount]. + ce = new_chan_extra(chan_extra_map, + scid, + MAX_CAP); + /* FIXME: features? */ + gossmap_local_addchan(renepay->local_gossmods, + src, dst, &scid, NULL); + gossmap_local_updatechan(renepay->local_gossmods, + &scid, + /* We assume any HTLC is allowed */ + AMOUNT_MSAT(0), MAX_CAP, + fee_base_msat, fee_proportional_millionths, + cltv_expiry_delta, + true, + dir); + } + + /* It is wrong to assume that this channel has sufficient capacity! + * Doing so leads to knowledge updates in which the known min liquidity + * is greater than the channel's capacity. */ + // chan_extra_can_send(chan_extra_map,scid,dir,amount); +} + +/* Add routehints provided by bolt11 */ +void uncertainty_network_add_routehints( + struct chan_extra_map *chan_extra_map, + struct renepay *renepay) +{ + struct payment const * const p = renepay->payment; + struct bolt11 *b11; + char *fail; + + b11 = + bolt11_decode(tmpctx, p->invstr, + plugin_feature_set(renepay->cmd->plugin), + p->description, chainparams, &fail); + if (b11 == NULL) + debug_err("add_routehints: Invalid bolt11: %s", fail); + + for (size_t i = 0; i < tal_count(b11->routes); i++) { + /* Each one, presumably, leads to the destination */ + const struct route_info *r = b11->routes[i]; + const struct node_id *end = & p->destination; + for (int j = tal_count(r)-1; j >= 0; j--) { + add_hintchan( + chan_extra_map, + renepay, &r[j].pubkey, end, + r[j].cltv_expiry_delta, + r[j].short_channel_id, + r[j].fee_base_msat, + r[j].fee_proportional_millionths); + end = &r[j].pubkey; + } + } +} + +/* Mirror the gossmap in the public uncertainty network. + * result: Every channel in gossmap must have associated data in chan_extra_map, + * while every channel in chan_extra_map is also registered in gossmap. + * */ +void uncertainty_network_update( + const struct gossmap *gossmap, + struct chan_extra_map *chan_extra_map) +{ + const tal_t* this_ctx = tal(tmpctx,tal_t); + + // For each chan in chan_extra_map remove if not in the gossmap + struct short_channel_id *del_list + = tal_arr(this_ctx,struct short_channel_id,0); + + 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)) + { + struct gossmap_chan * chan = gossmap_find_chan(gossmap,&ce->scid); + /* Only if the channel is not in the gossmap and there are not + * HTLCs pending we can remove it. */ + if(!chan && !chan_extra_is_busy(ce)) + { + // TODO(eduardo): is this efficiently implemented? + // otherwise i'll use a ccan list + tal_arr_expand(&del_list, ce->scid); + } + } + + for(size_t i=0;ipath_scids); i++) + { + chan_extra_sent_success( + chan_extra_map, + flow->path_scids[i], + flow->path_dirs[i], + flow->amounts[i]); + } +} +/* All parts up to erridx succeeded, so we know something about min + * capacity! */ +void uncertainty_network_channel_can_send( + struct chan_extra_map * chan_extra_map, + struct pay_flow *flow, + u32 erridx) +{ + for (size_t i = 0; i < erridx; i++) + { + chan_extra_can_send(chan_extra_map, + flow->path_scids[i], + flow->path_dirs[i], + + /* This channel can send all that was + * commited in HTLCs. + * Had we removed the commited amount then + * we would have to put here flow->amounts[i]. */ + AMOUNT_MSAT(0)); + } +} + +/* listpeerchannels gives us the certainty on local channels' capacity. Of course, + * this is racy and transient, but better than nothing! */ +bool uncertainty_network_update_from_listpeerchannels( + struct chan_extra_map * chan_extra_map, + struct node_id my_id, + struct renepay * renepay, + const char *buf, + const jsmntok_t *toks) +{ + struct payment * const p = renepay->payment; + const jsmntok_t *channels, *channel; + size_t i; + + if (json_get_member(buf, toks, "error")) + goto malformed; + + channels = json_get_member(buf, toks, "channels"); + if (!channels) + goto malformed; + + json_for_each_arr(i, channel, channels) { + struct short_channel_id scid; + const jsmntok_t *scidtok = json_get_member(buf, channel, "short_channel_id"); + /* If channel is still opening, this won't be there. + * Also it won't be in the gossmap, so there is + * no need to mark it as disabled. */ + if (!scidtok) + continue; + if (!json_to_short_channel_id(buf, scidtok, &scid)) + goto malformed; + + bool connected; + if(!json_to_bool(buf, + json_get_member(buf,channel,"peer_connected"), + &connected)) + goto malformed; + + if (!connected) { + debug_paynote(p, "local channel %s disabled:" + " peer disconnected", + type_to_string(tmpctx, + struct short_channel_id, + &scid)); + tal_arr_expand(&renepay->disabled, scid); + continue; + } + + const jsmntok_t *spendabletok, *dirtok,*statetok, *totaltok, + *peeridtok; + struct amount_msat spendable,capacity; + int dir; + + const struct node_id src=my_id; + struct node_id dst; + + spendabletok = json_get_member(buf, channel, "spendable_msat"); + dirtok = json_get_member(buf, channel, "direction"); + statetok = json_get_member(buf, channel, "state"); + totaltok = json_get_member(buf, channel, "total_msat"); + peeridtok = json_get_member(buf,channel,"peer_id"); + + if(spendabletok==NULL || dirtok==NULL || statetok==NULL || + totaltok==NULL || peeridtok==NULL) + goto malformed; + if (!json_to_msat(buf, spendabletok, &spendable)) + goto malformed; + if (!json_to_msat(buf, totaltok, &capacity)) + goto malformed; + if (!json_to_int(buf, dirtok,&dir)) + goto malformed; + if(!json_to_node_id(buf,peeridtok,&dst)) + goto malformed; + + /* Don't report opening/closing channels */ + if (!json_tok_streq(buf, statetok, "CHANNELD_NORMAL")) { + tal_arr_expand(&renepay->disabled, scid); + continue; + } + + struct chan_extra *ce = chan_extra_map_get(chan_extra_map, + scid); + + if(!ce) + { + /* this channel is not public, but it belongs to us */ + ce = new_chan_extra(chan_extra_map, + scid, + capacity); + /* FIXME: features? */ + gossmap_local_addchan(renepay->local_gossmods, + &src, &dst, &scid, NULL); + gossmap_local_updatechan(renepay->local_gossmods, + &scid, + + /* TODO(eduardo): does it + * matter to consider HTLC + * limits in our own channel? */ + AMOUNT_MSAT(0),capacity, + + /* fees = */0,0, + + /* TODO(eduardo): does it + * matter to set this delay? */ + /*delay=*/0, + true, + dir); + } + + // TODO(eduardo): this includes pending HTLC of previous + // payments! + /* We know min and max liquidity exactly now! */ + chan_extra_set_liquidity(chan_extra_map, + scid,dir,spendable); + } + return true; + +malformed: + return false; +} diff --git a/plugins/renepay/uncertainty_network.h b/plugins/renepay/uncertainty_network.h new file mode 100644 index 000000000000..24bd5a05c6b6 --- /dev/null +++ b/plugins/renepay/uncertainty_network.h @@ -0,0 +1,46 @@ +#ifndef LIGHTNING_PLUGINS_RENEPAY_UNCERTAINTY_NETWORK_H +#define LIGHTNING_PLUGINS_RENEPAY_UNCERTAINTY_NETWORK_H + +#include +#include +#include +#include + + +/* Checks the entire uncertainty network for invariant violations. */ +bool uncertainty_network_check_invariants(struct chan_extra_map *chan_extra_map); + +/* Add routehints provided by bolt11 */ +void uncertainty_network_add_routehints( + struct chan_extra_map *chan_extra_map, + struct renepay *renepay); + +/* Mirror the gossmap in the public uncertainty network. + * result: Every channel in gossmap must have associated data in chan_extra_map, + * while every channel in chan_extra_map is also registered in gossmap. + * */ +void uncertainty_network_update( + const struct gossmap *gossmap, + struct chan_extra_map *chan_extra_map); + +void uncertainty_network_flow_success( + struct chan_extra_map *chan_extra_map, + struct pay_flow *flow); + +/* All parts up to erridx succeeded, so we know something about min + * capacity! */ +void uncertainty_network_channel_can_send( + struct chan_extra_map * chan_extra_map, + struct pay_flow *flow, + u32 erridx); + +/* listpeerchannels gives us the certainty on local channels' capacity. Of course, + * this is racy and transient, but better than nothing! */ +bool uncertainty_network_update_from_listpeerchannels( + struct chan_extra_map * chan_extra_map, + struct node_id my_id, + struct renepay * renepay, + const char *buf, + const jsmntok_t *toks); + +#endif // LIGHTNING_PLUGINS_RENEPAY_UNCERTAINTY_NETWORK_H diff --git a/tests/test_renepay.py b/tests/test_renepay.py new file mode 100644 index 000000000000..138841a4d45b --- /dev/null +++ b/tests/test_renepay.py @@ -0,0 +1,227 @@ +from fixtures import * # noqa: F401,F403 +from fixtures import TEST_NETWORK +from pathlib import Path +from io import BytesIO +from pyln.client import RpcError, Millisatoshi +from pyln.proto.onion import TlvPayload +from pyln.testing.utils import EXPERIMENTAL_DUAL_FUND, FUNDAMOUNT, scid_to_int +from utils import ( + DEVELOPER, wait_for, only_one, sync_blockheight, TIMEOUT, + EXPERIMENTAL_FEATURES, VALGRIND, mine_funding_to_announce, first_scid +) +import copy +import os +import pytest +import random +import re +import string +import struct +import subprocess +import time +import unittest + +def test_simple(node_factory): + '''Testing simply paying a peer.''' + l1, l2 = node_factory.line_graph(2) + inv = l2.rpc.invoice(123000, 'test_renepay', 'description')['bolt11'] + before = int(time.time()) + details = l1.rpc.call('renepay',{'invstring':inv}) + after = time.time() + assert details['status'] == 'complete' + assert details['amount_msat'] == Millisatoshi(123000) + assert details['destination'] == l2.info['id'] + +def test_mpp(node_factory): + '''Test paying a remote node using two routes. + 1----2----4 + | | + 3----5----6 + Try paying 1.2M sats from 1 to 6. + ''' + opts = [ + {'disable-mpp': None, 'fee-base':0, 'fee-per-satoshi':0}, + ] + l1, l2, l3, l4, l5,l6 = node_factory.get_nodes(6, opts=opts*6) + node_factory.join_nodes([l1, l2, l4, l6], + wait_for_announce=True,fundamount=1000000) + node_factory.join_nodes([l1, l3, l5, l6], + wait_for_announce=True,fundamount=1000000) + + send_amount = Millisatoshi('1200000sat') + inv = l6.rpc.invoice(send_amount, 'test_renepay', 'description')['bolt11'] + details = l1.rpc.call('renepay',{'invstring':inv}) + assert details['status'] == 'complete' + assert details['amount_msat'] == send_amount + assert details['destination'] == l6.info['id'] + +def test_errors(node_factory): + opts = [ + {'disable-mpp': None, 'fee-base':0, 'fee-per-satoshi':0}, + ] + l1, l2, l3, l4, l5,l6 = node_factory.get_nodes(6, opts=opts*6) + send_amount = Millisatoshi('21sat') + inv = l6.rpc.invoice(send_amount, 'test_renepay', 'description')['bolt11'] + + failmsg = r'We don\'t have any channels' + with pytest.raises(RpcError, match=failmsg) as err: + l1.rpc.call('renepay',{'invstring':inv}) + node_factory.join_nodes([l1, l2, l4], + wait_for_announce=True,fundamount=1000000) + node_factory.join_nodes([l1, l3, l5], + wait_for_announce=True,fundamount=1000000) + + failmsg = r'Destination is unreacheable in the network gossip.' + with pytest.raises(RpcError, match=failmsg) as err: + l1.rpc.call('renepay',{'invstring':inv}) + + node_factory.join_nodes([l4,l6], + wait_for_announce=True,fundamount=1000000) + node_factory.join_nodes([l5,l6], + wait_for_announce=True,fundamount=1000000) + + details = l1.rpc.call('renepay',{'invstring':inv, 'use_shadow':False}) + assert details['status'] == 'complete' + assert details['amount_msat'] == send_amount + assert details['destination'] == l6.info['id'] + + +### SAME TESTS USED IN THE STANDARD PAY COMMAND ### + +@pytest.mark.developer("needs to deactivate shadow routing") +@pytest.mark.openchannel('v1') +@pytest.mark.openchannel('v2') +def test_pay(node_factory): + l1, l2 = node_factory.line_graph(2) + + inv = l2.rpc.invoice(123000, 'test_pay', 'description')['bolt11'] + before = int(time.time()) + details = l1.rpc.call('renepay',{'invstring':inv,'use_shadow':False}) + after = time.time() + preimage = details['payment_preimage'] + assert details['status'] == 'complete' + assert details['amount_msat'] == Millisatoshi(123000) + assert details['destination'] == l2.info['id'] + assert details['created_at'] >= before + assert details['created_at'] <= after + + invoices = l2.rpc.listinvoices('test_pay')['invoices'] + assert len(invoices) == 1 + invoice = invoices[0] + assert invoice['status'] == 'paid' and invoice['paid_at'] >= before and invoice['paid_at'] <= after + + # Repeat payments are NOPs (if valid): we can hand null. + l1.rpc.call('renepay',{'invstring':inv,'use_shadow':False}) + # This won't work: can't provide an amount (even if correct!) + with pytest.raises(RpcError): + l1.rpc.call('renepay',{'invstring':inv,'amount_msat':123000}) + with pytest.raises(RpcError): + l1.rpc.call('renepay',{'invstring':inv,'amount_msat':122000}) + + # Check pay_index is not null + outputs = l2.db_query('SELECT pay_index IS NOT NULL AS q FROM invoices WHERE label="label";') + assert len(outputs) == 1 and outputs[0]['q'] != 0 + + # Check payment of any-amount invoice. + for i in range(5): + label = "any{}".format(i) + inv2 = l2.rpc.invoice("any", label, 'description')['bolt11'] + # Must provide an amount! + with pytest.raises(RpcError): + l1.rpc.call('renepay',{'invstring':inv2,'use_shadow':False}) + + l1.rpc.call('renepay',{'invstring':inv2,'use_shadow':False,'amount_msat':random.randint(1000,999999)}) + + # Should see 6 completed payments + assert len(l1.rpc.listsendpays()['payments']) == 6 + + # Test listsendpays indexed by bolt11. + payments = l1.rpc.listsendpays(inv)['payments'] + assert len(payments) == 1 and payments[0]['payment_preimage'] == preimage + + # let's buy some time to let bkpr receive coin_movement notification + ret = l1.rpc.call('renepaystatus') + + # Check channels apy summary view of channel activity + apys_1 = l1.rpc.bkpr_channelsapy()['channels_apy'] + apys_2 = l2.rpc.bkpr_channelsapy()['channels_apy'] + + assert apys_1[0]['channel_start_balance_msat'] == apys_2[0]['channel_start_balance_msat'] + assert apys_1[0]['channel_start_balance_msat'] == apys_1[0]['our_start_balance_msat'] + assert apys_2[0]['our_start_balance_msat'] == Millisatoshi(0) + assert apys_1[0]['routed_out_msat'] == apys_2[0]['routed_in_msat'] + assert apys_1[0]['routed_in_msat'] == apys_2[0]['routed_out_msat'] + + +@pytest.mark.developer("needs to deactivate shadow routing") +def test_amounts(node_factory): + l1, l2 = node_factory.line_graph(2) + inv = l2.rpc.invoice(Millisatoshi(123456), 'test_pay_amounts', 'description')['bolt11'] + + invoice = only_one(l2.rpc.listinvoices('test_pay_amounts')['invoices']) + + assert isinstance(invoice['amount_msat'], Millisatoshi) + assert invoice['amount_msat'] == Millisatoshi(123456) + + l1.rpc.call('renepay',{'invstring':inv, 'use_shadow':False}) + + invoice = only_one(l2.rpc.listinvoices('test_pay_amounts')['invoices']) + assert isinstance(invoice['amount_received_msat'], Millisatoshi) + assert invoice['amount_received_msat'] >= Millisatoshi(123456) + + +@pytest.mark.developer("needs to deactivate shadow routing") +def test_limits(node_factory): + ''' + Topology: + 1----2----4 + | | + 3----5----6 + ''' + opts = [ + {'disable-mpp': None, 'fee-base':0, 'fee-per-satoshi':100}, + ] + l1, l2, l3, l4, l5,l6 = node_factory.get_nodes(6, opts=opts*6) + node_factory.join_nodes([l1, l2, l4, l6], + wait_for_announce=True,fundamount=1000000) + node_factory.join_nodes([l1, l3, l5, l6], + wait_for_announce=True,fundamount=1000000) + + inv = l4.rpc.invoice('any','any','description') + l2.rpc.call('pay', {'bolt11': inv['bolt11'], 'amount_msat':500000000}) + inv = l5.rpc.invoice('any','any','description') + l3.rpc.call('pay', {'bolt11': inv['bolt11'], 'amount_msat':500000000}) + + # FIXME: pylightning should define these! + # PAY_STOPPED_RETRYING = 210 + PAY_ROUTE_NOT_FOUND=205 + + inv = l6.rpc.invoice("any", "any", 'description') + + # Fee too high. + failmsg = r'Fee exceeds our fee budget' + with pytest.raises(RpcError, match=failmsg) as err: + l1.rpc.call('renepay', {'invstring': inv['bolt11'], 'amount_msat': 1000000, 'maxfee': 1}) + assert err.value.error['code'] == PAY_ROUTE_NOT_FOUND + # TODO(eduardo): which error code shall we use here? + + # TODO(eduardo): shall we list attempts in renepay? + # status = l1.rpc.call('renepaystatus', {'invstring':inv['bolt11']})['paystatus'][0]['attempts'] + + failmsg = r'CLTV delay exceeds our CLTV budget' + # Delay too high. + with pytest.raises(RpcError, match=failmsg) as err: + l1.rpc.call('renepay', {'invstring': inv['bolt11'], 'amount_msat': 1000000, 'maxdelay': 0}) + assert err.value.error['code'] == PAY_ROUTE_NOT_FOUND + + inv2 = l6.rpc.invoice("800000sat", "inv2", 'description') + failmsg = r'Probability is too small' + with pytest.raises(RpcError, match=failmsg) as err: + l1.rpc.call('renepay', {'invstring': inv2['bolt11'],'min_prob_success': '0.5'}) + assert err.value.error['code'] == PAY_ROUTE_NOT_FOUND + + # if we try again we can finish this payment + l1.rpc.call('renepay', {'invstring': inv2['bolt11'],'min_prob_success':0}) + invoice = only_one(l6.rpc.listinvoices('inv2')['invoices']) + assert isinstance(invoice['amount_received_msat'], Millisatoshi) + assert invoice['amount_received_msat'] >= Millisatoshi('800000sat') +