renepay: an experimental payment plugin

Signed-off-by: Lagrang3 <eduardo.quintana@pm.me>
Changelog-Added: Plugins: `renepay`: an experimental pay plugin implementing Pickhardt payments (`renepay` and `renepaystatus`).
This commit is contained in:
Rusty Russell 2023-07-31 11:21:22 +09:30
parent c02f175a75
commit b8ca193606
26 changed files with 8483 additions and 0 deletions

View File

@ -100,6 +100,7 @@ C_PLUGINS := \
plugins/offers \
plugins/pay \
plugins/txprepare \
plugins/cln-renepay \
plugins/spenderp
PY_PLUGINS := \
@ -177,6 +178,7 @@ PLUGIN_COMMON_OBJS := \
wire/towire.o
include plugins/bkpr/Makefile
include plugins/renepay/Makefile
# Make sure these depend on everything.
ALL_C_SOURCES += $(PLUGIN_ALL_SRC)

16
plugins/renepay/Makefile Normal file
View File

@ -0,0 +1,16 @@
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.
ALL_C_SOURCES += $(PLUGIN_RENEPAY_SRC)
ALL_C_HEADERS += $(PLUGIN_RENEPAY_HDRS)
# Make all plugins depend on all plugin headers, for simplicity.
$(PLUGIN_RENEPAY_OBJS): $(PLUGIN_RENEPAY_HDRS)
plugins/cln-renepay: $(PLUGIN_RENEPAY_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
include plugins/renepay/test/Makefile

51
plugins/renepay/debug.c Normal file
View File

@ -0,0 +1,51 @@
#include "config.h"
#include <plugins/renepay/debug.h>
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)
{
FILE *f = fopen(fname,"a");
fprintf(f,"calling function: %s\n",fun);
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);
}

54
plugins/renepay/debug.h Normal file
View File

@ -0,0 +1,54 @@
#ifndef LIGHTNING_PLUGINS_RENEPAY_DEBUG_H
#define LIGHTNING_PLUGINS_RENEPAY_DEBUG_H
#include "config.h"
#include <ccan/json_out/json_out.h>
#include <common/json_param.h>
#include <common/json_stream.h>
#include <common/type_to_string.h>
#include <plugins/libplugin.h>
#include <plugins/renepay/pay.h>
#include <stdio.h>
#include <wire/peer_wire.h>
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
/* Debugging information goes either to payment notes or to lightningd log. */
#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 /* LIGHTNING_PLUGINS_RENEPAY_DEBUG_H */

173
plugins/renepay/dijkstra.c Normal file
View File

@ -0,0 +1,173 @@
#include "config.h"
#include <plugins/renepay/dijkstra.h>
static const s64 INFINITE = INT64_MAX;
/* Required a global dijkstra for gheap. */
static struct dijkstra *global_dijkstra;
/* The heap comparer for Dijkstra search. Since the top element must be the one
* with the smallest distance, we use the operator >, rather than <. */
static int dijkstra_less_comparer(
const void *const ctx UNUSED,
const void *const a,
const void *const b)
{
return global_dijkstra->distance[*(u32*)a]
> global_dijkstra->distance[*(u32*)b];
}
/* The heap move operator for Dijkstra search. */
static void dijkstra_item_mover(void *const dst, const void *const src)
{
u32 src_idx = *(u32*)src;
*(u32*)dst = src_idx;
// we keep track of the pointer position of each element in the heap,
// for easy update.
global_dijkstra->heapptr[src_idx] = dst;
}
/* Destructor for global dijkstra. The valid free state is signalled with a
* NULL ptr. */
static void dijkstra_destroy(struct dijkstra *ptr UNUSED)
{
global_dijkstra=NULL;
}
/* Manually release dijkstra resources. */
void dijkstra_free(void)
{
if(global_dijkstra)
{
global_dijkstra = tal_free(global_dijkstra);
}
}
/* 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;i<max_num_nodes;++i)
{
global_dijkstra->distance[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;
}

View File

@ -0,0 +1,49 @@
#ifndef LIGHTNING_PLUGINS_RENEPAY_DIJKSTRA_H
#define LIGHTNING_PLUGINS_RENEPAY_DIJKSTRA_H
#include "config.h"
#include <ccan/short_types/short_types.h>
#include <ccan/tal/tal.h>
#include <gheap.h>
/* 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);
/* Manually release dijkstra 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 */

828
plugins/renepay/flow.c Normal file
View File

@ -0,0 +1,828 @@
#include "config.h"
#include <assert.h>
#include <ccan/asort/asort.h>
#include <ccan/tal/str/str.h>
#include <ccan/tal/tal.h>
#include <common/type_to_string.h>
#include <math.h>
#include <plugins/renepay/debug.h>
#include <plugins/renepay/flow.h>
#include <stdio.h>
#ifndef SUPERVERBOSE
#define SUPERVERBOSE(...)
#else
#define SUPERVERBOSE_ENABLED 1
#endif
bool chan_extra_is_busy(struct chan_extra const * const ce)
{
if(ce==NULL)return false;
return ce->half[0].num_htlcs || ce->half[1].num_htlcs;
}
const char *fmt_chan_extra_map(
const tal_t *ctx,
struct chan_extra_map* chan_extra_map)
{
tal_t *this_ctx = tal(ctx,tal_t);
char *buff = tal_fmt(ctx,"Uncertainty network:\n");
struct chan_extra_map_iter it;
for(struct chan_extra *ch = chan_extra_map_first(chan_extra_map,&it);
ch;
ch=chan_extra_map_next(chan_extra_map,&it))
{
const char *scid_str =
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;
}
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);
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;
}
/* 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)
);
}
}
/* 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(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));
}
chan_extra_can_send_(ce,dir,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)
{
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 *
get_chan_extra_half_by_scid(struct chan_extra_map *chan_extra_map,
const struct short_channel_id scid,
int dir)
{
struct chan_extra *ce;
ce = chan_extra_map_get(chan_extra_map, scid);
if (!ce)
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,
struct chan_extra_map *chan_extra_map,
const struct gossmap_chan *chan,
int dir)
{
return get_chan_extra_half_by_scid(chan_extra_map,
gossmap_chan_scid(gossmap, chan),
dir);
}
// 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)
{
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;
}
/* 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<a: 1.
* for b>=f>=a: (b-f)/(b-a)
* for b<f: 0.
*
* When x>0 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 and f<a-x: 1.
* for x<a and f>=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);
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));
}
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)
{
for (size_t i = 0; i < tal_count(flow->path); 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_sub(&h->htlc_total, h->htlc_total, flow->amounts[i]))
{
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)
{
debug_err("%s could not decrease HTLC count.",
__PRETTY_FUNCTION__);
}
h->num_htlcs--;
}
}
// 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)
{
for(size_t i=0;i<tal_count(flows);++i)
{
remove_completed_flow(gossmap,chan_extra_map,flows[i]);
}
}
// TODO(eduardo): remove this function, is a duplicate
void commit_flow(
const struct gossmap *gossmap,
struct chan_extra_map *chan_extra_map,
struct flow *flow)
{
for (size_t i = 0; i < tal_count(flow->path); 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;i<tal_count(flows);++i)
{
commit_flow(gossmap,chan_extra_map,flows[i]);
}
}
/* 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,
struct amount_msat delivered)
{
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--) {
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;
flow->success_prob
*= edge_probability(h->known_min, h->known_max,
h->htlc_total,
delivered);
if (!amount_msat_add_fee(&delivered,
flow_edge(flow, i)->base_fee,
flow_edge(flow, i)->proportional_fee))
{
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;i<max_num_chans;++i)
{
in_flight[i].half[0]=in_flight[i].half[1]=AMOUNT_MSAT(0);
}
for(size_t i=0;i<tal_count(flows);++i)
{
const struct flow* f = flows[i];
for(size_t j=0;j<tal_count(f->path);++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)
{
if (amount_msat_less(*a, *b))
return -1;
if (amount_msat_greater(*a, *b))
return 1;
return 0;
}
static int cmp_amount_sat(const struct amount_sat *a,
const struct amount_sat *b,
void *unused)
{
if (amount_sat_less(*a, *b))
return -1;
if (amount_sat_greater(*a, *b))
return 1;
return 0;
}
/* Get median feerates and capacities. */
static void get_medians(const struct gossmap *gossmap,
struct amount_msat amount,
struct amount_msat *median_capacity,
struct amount_msat *median_fee)
{
size_t num_caps, num_fees;
struct amount_sat *caps;
struct amount_msat *fees;
caps = tal_arr(tmpctx, struct amount_sat,
gossmap_max_chan_idx(gossmap));
fees = tal_arr(tmpctx, struct amount_msat,
gossmap_max_chan_idx(gossmap) * 2);
num_caps = num_fees = 0;
for (struct gossmap_chan *c = gossmap_first_chan(gossmap);
c;
c = gossmap_next_chan(gossmap, c)) {
/* If neither feerate is set, it's not useful */
if (!gossmap_chan_set(c, 0) && !gossmap_chan_set(c, 1))
continue;
/* Insufficient capacity? Not useful */
if (!gossmap_chan_get_capacity(gossmap, c, &caps[num_caps]))
continue;
if (amount_msat_greater_sat(amount, caps[num_caps]))
continue;
num_caps++;
for (int dir = 0; dir <= 1; dir++) {
if (!gossmap_chan_set(c, dir))
continue;
if (!amount_msat_fee(&fees[num_fees],
amount,
c->half[dir].base_fee,
c->half[dir].proportional_fee))
continue;
num_fees++;
}
}
asort(caps, num_caps, cmp_amount_sat, NULL);
/* If there are no channels, it doesn't really matter, but
* this avoids div by 0 */
if (!num_caps)
*median_capacity = amount;
else if (!amount_sat_to_msat(median_capacity, caps[num_caps / 2]))
{
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);
else
*median_fee = fees[num_fees / 2];
}
double derive_mu(const struct gossmap *gossmap,
struct amount_msat amount,
double frugality)
{
struct amount_msat median_capacity, median_fee;
double cap_plus_one;
get_medians(gossmap, amount, &median_capacity, &median_fee);
cap_plus_one = median_capacity.millisatoshis + 1; /* Raw: derive_mu */
return -log((cap_plus_one - amount.millisatoshis) /* Raw: derive_mu */
/ cap_plus_one)
* frugality
/* +1 in case median fee is zero... */
/ (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 CLTV 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

319
plugins/renepay/flow.h Normal file
View File

@ -0,0 +1,319 @@
#ifndef LIGHTNING_PLUGINS_RENEPAY_FLOW_H
#define LIGHTNING_PLUGINS_RENEPAY_FLOW_H
#include "config.h"
#include <bitcoin/short_channel_id.h>
#include <ccan/htable/htable_type.h>
#include <common/amount.h>
#include <common/gossmap.h>
#include <plugins/renepay/payment.h>
// 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 */
size_t num_htlcs;
/* The total size of those HTLCs */
struct amount_msat htlc_total;
/* The known minimum / maximum capacity (if nothing known, 0/capacity */
struct amount_msat known_min, known_max;
} half[2];
};
bool chan_extra_is_busy(struct chan_extra const * const ce);
static inline const struct short_channel_id
chan_extra_scid(const struct chan_extra *cd)
{
return cd->scid;
}
static inline size_t hash_scid(const struct short_channel_id scid)
{
/* scids cost money to generate, so simple hash works here */
return (scid.u64 >> 32)
^ (scid.u64 >> 16)
^ scid.u64;
}
static inline bool chan_extra_eq_scid(const struct chan_extra *cd,
const struct short_channel_id scid)
{
return short_channel_id_eq(&scid, &cd->scid);
}
HTABLE_DEFINE_TYPE(struct chan_extra,
chan_extra_scid, hash_scid, chan_extra_eq_scid,
chan_extra_map);
/* Helpers for chan_extra_map */
/* Channel knowledge invariants:
*
* 0<=a<=b<=capacity
*
* a_inv = capacity-b
* b_inv = capacity-a
*
* where a,b are the known minimum and maximum liquidities, and a_inv and b_inv
* are the known minimum and maximum liquidities for the channel in the opposite
* direction.
*
* Knowledge update operations can be:
*
* 1. set liquidity (x)
* (a,b) -> (x,x)
*
* The entropy is minimum here (=0).
*
* 2. can send (x):
* xb = min(x,capacity)
* (a,b) -> (max(a,xb),max(b,xb))
*
* If x<=a then there is no new knowledge and the entropy remains
* the same.
* If x>a the entropy decreases.
*
*
* 3. can't send (x):
* xb = max(0,x-1)
* (a,b) -> (min(a,xb),min(b,xb))
*
* If x>b there is no new knowledge and the entropy remains.
* If x<=b then the entropy decreases.
*
* 4. sent success (x):
* (a,b) -> (max(0,a-x),max(0,b-x))
*
* If x<=a there is no new knowledge and the entropy remains.
* If a<x then the entropy decreases.
*
* 5. relax (x,y):
*
* (a,b) -> (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 amount_msat_less(a,b) ? 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 amount_msat_greater(a,b) ? 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);
/* An actual partial flow. */
struct flow {
struct gossmap_chan const **path;
/* The directions to traverse. */
int *dirs;
/* Amounts for this flow (fees mean this shrinks across path). */
struct amount_msat *amounts;
/* Probability of success (0-1) */
double success_prob;
};
/* Helper to access the half chan at flow index idx */
const struct half_chan *flow_edge(const struct flow *flow, size_t idx);
/* A big number, meaning "don't bother" (not infinite, since you may add) */
#define FLOW_INF_COST 100000000.0
/* Cost function to send @f msat through @c in direction @dir,
* given we already have a flow of prev_flow. */
double flow_edge_cost(const struct gossmap *gossmap,
const struct gossmap_chan *c, int dir,
const struct amount_msat known_min,
const struct amount_msat known_max,
struct amount_msat prev_flow,
struct amount_msat f,
double mu,
double basefee_penalty,
double delay_riskfactor);
/* 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:
*
* -log((c_e + 1 - f_e) / (c_e + 1)) + μ fee
*
* This raises the question of how to set mu? The left term is a
* logrithmic failure probability, the right term is the fee in
* millisats.
*
* We want a more "usable" measure of frugality (fr), where fr = 1
* means that the two terms are roughly equal, and fr < 1 means the
* probability is more important, and fr > 1 means the fee is more
* important.
*
* For this we take the current payment amount and the median channel
* capacity and feerates:
*
* -log((median_cap + 1 - f_e) / (median_cap + 1)) = μ (1/fr) median_fee
*
* => μ = -log((median_cap + 1 - f_e) / (median_cap + 1)) * fr / median_fee
*
* But this is slightly too naive! If we're trying to make a payment larger
* than the median, this is undefined; and grows hugely when we're near the median.
* In fact, it should be "the median of all channels larger than the amount",
* which is what we calculate here.
*
* Turns out that in the real network:
* - median_cap = 1250800000
* - median_feerate = 51
*
* And the log term at the 10th percentile capacity is about 0.125 of the median,
* and at the 90th percentile capacity the log term is about 12.5 the value at the median.
*
* In other words, we expose a simple frugality knob with reasonable
* range 0.1 (don't care about fees) to 10 (fees before probability),
* and generate our μ from there.
*/
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 */

195
plugins/renepay/heap.h Normal file
View File

@ -0,0 +1,195 @@
#ifndef LIGHTNING_PLUGINS_RENEPAY_HEAP_H
#define LIGHTNING_PLUGINS_RENEPAY_HEAP_H
#include "config.h"
#include <ccan/tal/tal.h>
#include <gheap.h>
#include <stdint.h>
/* 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<item) }, all the elements that are greater or
* equal item,
* then p in X, and for every x in X: !(x<p), p is the smallest.*/
size_t gheap_upper_bound(const struct gheap_ctx *ctx,
const void *base, size_t heap_size, void *item);
struct heap_data
{
u32 idx;
s64 distance;
};
struct heap
{
size_t size;
size_t max_size;
struct heap_data *data;
struct gheap_ctx gheap_ctx;
};
struct heap* heap_new(const tal_t *ctx, const size_t max_capacity);
void heap_insert(struct heap* heap, u32 idx, s64 distance);
void heap_update(struct heap* heap, u32 idx, s64 old_distance,s64 new_distance);
bool heap_empty(const struct heap* heap);
void heap_pop(struct heap* heap);
struct heap_data * heap_top(const struct heap * heap);
//------------------------------
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 !(x<item) */
size_t gheap_upper_bound(const struct gheap_ctx *ctx,
const void *base, size_t heap_size, void *item)
{
const size_t fanout = ctx->fanout;
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<item, so x<=root<item is true for every node
return heap_size;
}
size_t last=0;
// the root is an upper bound, now let's go down
while(1)
{
// last is an upper bound, seach for a smaller one
size_t first_child = gheap_get_child_index(ctx,last);
size_t best_child = last;
for(size_t i=0;i<fanout;++i)
{
size_t child = i+first_child;
if(child>=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 */

1507
plugins/renepay/mcf.c Normal file

File diff suppressed because it is too large Load Diff

71
plugins/renepay/mcf.h Normal file
View File

@ -0,0 +1,71 @@
#ifndef LIGHTNING_PLUGINS_RENEPAY_MCF_H
#define LIGHTNING_PLUGINS_RENEPAY_MCF_H
#include "config.h"
#include <ccan/bitmap/bitmap.h>
#include <common/amount.h>
#include <common/gossmap.h>
struct chan_extra_map;
enum {
RENEPAY_ERR_OK,
// No feasible flow found, either there is not enough known liquidity (or capacity)
// in the channels to complete the payment
RENEPAY_ERR_NOFEASIBLEFLOW,
// There is at least one feasible flow, but the the cheapest solution that we
// found is too expensive, we return the result anyways.
RENEPAY_ERR_NOCHEAPFLOW
};
/**
* optimal_payment_flow - API for min cost flow function(s).
* @ctx: context to allocate returned flows from
* @gossmap: the gossip map
* @source: the source to start from
* @target: the target to pay
* @chan_extra_map: hashtable of extra per-channel information
* @disabled: NULL, or a bitmap by channel index of channels not to use.
* @amount: the amount we want to reach @target
*
* @max_fee: the maximum allowed in fees
*
* @min_probability: minimum probability accepted
*
* @delay_feefactor converts 1 block delay into msat, as if it were an additional
* fee. So if a CLTV delay on a node is 5 blocks, that's treated as if it
* were a fee of 5 * @delay_feefactor.
*
* @base_fee_penalty: factor to compute additional proportional cost from each
* unit of base fee. So #base_fee_penalty will be added to the effective
* proportional fee for each msat of base fee.
*
* effective_ppm = proportional_fee + base_fee_msat * base_fee_penalty
*
* @prob_cost_factor: factor used to monetize the probability cost. It is
* defined as the number of ppm (parts per million of the total payment) we
* are willing to pay to improve the probability of success by 0.1%.
*
* k_microsat = floor(1000*prob_cost_factor * payment_sat)
*
* this k is used to compute a prob. cost in units of microsats
*
* cost(payment) = - k_microsat * log Prob(payment)
*
* Return a series of subflows which deliver amount to target, or NULL.
*/
struct flow** minflow(
const tal_t *ctx,
struct gossmap *gossmap,
const struct gossmap_node *source,
const struct gossmap_node *target,
struct chan_extra_map *chan_extra_map,
const bitmap *disabled,
struct amount_msat amount,
struct amount_msat max_fee,
double min_probability,
double delay_feefactor,
double base_fee_penalty,
u32 prob_cost_factor);
#endif /* LIGHTNING_PLUGINS_RENEPAY_MCF_H */

1792
plugins/renepay/pay.c Normal file

File diff suppressed because it is too large Load Diff

117
plugins/renepay/pay.h Normal file
View File

@ -0,0 +1,117 @@
#ifndef LIGHTNING_PLUGINS_RENEPAY_PAY_H
#define LIGHTNING_PLUGINS_RENEPAY_PAY_H
#include "config.h"
#include <ccan/list/list.h>
#include <common/node_id.h>
#include <plugins/libplugin.h>
#include <plugins/renepay/flow.h>
#include <plugins/renepay/payment.h>
// TODO(eduardo): renepaystatus should be similar to paystatus
// TODO(eduardo): MCF should consider pending HTLCs occupy some capacity in the
// routing channels.
// 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 {
/* From libplugin */
struct plugin *plugin;
/* Public key of this node. */
struct node_id my_id;
/* Map of gossip. */
struct gossmap *gossmap;
/* Settings for maxdelay */
unsigned int maxdelay_default;
/* Offers support */
bool exp_offers;
/* All the struct payment */
struct list_head payments;
/* Per-channel metadata: some persists between payments */
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;
};
/* Set in init */
extern struct pay_plugin * const pay_plugin;
/* Accumulate or panic on overflow */
#define amount_msat_accumulate(dst, src) \
amount_msat_accumulate_((dst), (src), stringify(dst), stringify(src))
#define amount_msat_reduce(dst, src) \
amount_msat_reduce_((dst), (src), stringify(dst), stringify(src))
void amount_msat_accumulate_(struct amount_msat *dst,
struct amount_msat src,
const char *dstname,
const char *srcname);
void amount_msat_reduce_(struct amount_msat *dst,
struct amount_msat src,
const char *dstname,
const char *srcname);
#endif /* LIGHTNING_PLUGINS_RENEPAY_PAY_H */

633
plugins/renepay/pay_flow.c Normal file
View File

@ -0,0 +1,633 @@
/* Routines to get suitable pay_flow array from pay constraints */
#include "config.h"
#include <ccan/json_out/json_out.h>
#include <ccan/tal/str/str.h>
#include <common/gossmap.h>
#include <common/pseudorand.h>
#include <common/type_to_string.h>
#include <errno.h>
#include <plugins/libplugin.h>
#include <plugins/renepay/mcf.h>
#include <plugins/renepay/pay.h>
#include <plugins/renepay/pay_flow.h>
/* BOLT #7:
*
* If a route is computed by simply routing to the intended recipient and summing
* the `cltv_expiry_delta`s, then it's possible for intermediate nodes to guess
* their position in the route. Knowing the CLTV of the HTLC, the surrounding
* network topology, and the `cltv_expiry_delta`s gives an attacker a way to guess
* the intended recipient. Therefore, it's highly desirable to add a random offset
* to the CLTV that the intended recipient will receive, which bumps all CLTVs
* along the route.
*
* In order to create a plausible offset, the origin node MAY start a limited
* random walk on the graph, starting from the intended recipient and summing the
* `cltv_expiry_delta`s, and use the resulting sum as the offset.
* This effectively creates a _shadow route extension_ to the actual route and
* provides better protection against this attack vector than simply picking a
* random offset would.
*/
/* There's little benefit in doing this per-flow, since you can
* correlate flows so trivially, but it's good practice for when we
* have PTLCs and that's not true. */
#define MAX_SHADOW_LEN 3
/* Returns CLTV, and fills in *shadow_fee, based on extending the path */
static u32 shadow_one_flow(const struct gossmap *gossmap,
const struct flow *f,
struct amount_msat *shadow_fee)
{
size_t numpath = tal_count(f->amounts);
struct amount_msat amount = f->amounts[numpath-1];
struct gossmap_node *n;
size_t hop;
struct gossmap_chan *chans[MAX_SHADOW_LEN];
int dirs[MAX_SHADOW_LEN];
u32 shadow_delay = 0;
/* Start at end of path */
n = gossmap_nth_node(gossmap, f->path[numpath-1], !f->dirs[numpath-1]);
/* We only create shadow for extra CLTV delays, *not* for
* amounts. This is because with MPP our amounts are random
* looking already. */
for (hop = 0; hop < MAX_SHADOW_LEN && pseudorand(1); hop++) {
/* Try for a believable channel up to 10 times, then stop */
for (size_t i = 0; i < 10; i++) {
struct amount_sat cap;
chans[hop] = gossmap_nth_chan(gossmap, n, pseudorand(n->num_chans),
&dirs[hop]);
if (!gossmap_chan_set(chans[hop], dirs[hop])
|| !gossmap_chan_get_capacity(gossmap, chans[hop], &cap)
/* This test is approximate, since amount would differ */
|| amount_msat_less_sat(amount, cap)) {
chans[hop] = NULL;
continue;
}
}
if (!chans[hop])
break;
shadow_delay += chans[hop]->half[dirs[hop]].delay;
n = gossmap_nth_node(gossmap, chans[hop], !dirs[hop]);
}
/* If we were actually trying to get amount to end of shadow,
* what would we be paying to the "intermediary" node (real dest) */
for (int i = (int)hop - 1; i >= 0; i--)
if (!amount_msat_add_fee(&amount,
chans[i]->half[dirs[i]].base_fee,
chans[i]->half[dirs[i]].proportional_fee))
/* Ignore: treats impossible event as zero fee. */
;
/* Shouldn't happen either */
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),
type_to_string(tmpctx, struct amount_msat,
&f->amounts[numpath-1]));
return shadow_delay;
}
static bool add_to_amounts(const struct gossmap *gossmap,
struct flow *f,
struct amount_msat maxspend,
struct amount_msat additional)
{
struct amount_msat *amounts;
size_t num = tal_count(f->amounts);
/* Recalculate amounts backwards */
amounts = tal_arr(tmpctx, struct amount_msat, num);
if (!amount_msat_add(&amounts[num-1], f->amounts[num-1], additional))
return false;
for (int i = num-2; i >= 0; i--) {
amounts[i] = amounts[i+1];
if (!amount_msat_add_fee(&amounts[i],
flow_edge(f, i)->base_fee,
flow_edge(f, i)->proportional_fee))
return false;
}
/* Do we now exceed budget? */
if (amount_msat_greater(amounts[0], maxspend))
return false;
/* OK, replace amounts */
tal_free(f->amounts);
f->amounts = tal_steal(f, amounts);
return true;
}
static u64 flow_delay(const struct flow *flow)
{
u64 delay = 0;
for (size_t i = 0; i < tal_count(flow->path); i++)
delay += flow->path[i]->half[flow->dirs[i]].delay;
return delay;
}
/* This enhances f->amounts, and returns per-flow cltvs */
static u32 *shadow_additions(const tal_t *ctx,
const struct gossmap *gossmap,
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 */
final_cltvs = tal_arr(ctx, u32, tal_count(flows));
for (size_t i = 0; i < tal_count(flows); i++)
final_cltvs[i] = p->final_cltv;
/* DEVELOPER can disable this */
if (!p->use_shadow)
return final_cltvs;
for (size_t i = 0; i < tal_count(flows); i++) {
u32 shadow_delay;
struct amount_msat shadow_fee;
shadow_delay = shadow_one_flow(gossmap, flows[i],
&shadow_fee);
if (flow_delay(flows[i]) + shadow_delay > p->maxdelay) {
debug_paynote(p, "No shadow for flow %zu/%zu:"
" delay would add %u to %"PRIu64", exceeding max delay.",
i, tal_count(flows),
shadow_delay,
flow_delay(flows[i]));
continue;
}
/* We don't need to add fee amounts to obfuscate most payments
* when we're using MPP, since we randomly split amounts. But
* if this really is the entire thing, we want to, since
* people use round numbers of msats in invoices. */
if (is_entire_payment && tal_count(flows) == 1) {
if (!add_to_amounts(gossmap, flows[i], p->maxspend,
shadow_fee)) {
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,
&shadow_fee),
type_to_string(tmpctx, struct amount_msat,
&flows[i]->amounts[0]),
type_to_string(tmpctx, struct amount_msat,
&p->maxspend));
} else {
debug_paynote(p, "No MPP, so added %s shadow fee",
type_to_string(tmpctx, struct amount_msat,
&shadow_fee));
}
}
final_cltvs[i] += shadow_delay;
debug_paynote(p, "Shadow route on flow %zu/%zu added %u block delay. now %u",
i, tal_count(flows), shadow_delay, final_cltvs[i]);
}
return final_cltvs;
}
/* Calculates delays and converts to scids. Frees flows. Caller is responsible
* for removing resultings flows from the chan_extra_map. */
static struct pay_flow **flows_to_pay_flows(struct payment *payment,
struct gossmap *gossmap,
struct flow **flows STEALS,
const u32 *final_cltvs,
u64 *next_partid)
{
struct pay_flow **pay_flows
= tal_arr(payment, struct pay_flow *, tal_count(flows));
for (size_t i = 0; i < tal_count(flows); i++) {
struct flow *f = flows[i];
struct pay_flow *pf = tal(pay_flows, struct pay_flow);
size_t plen;
plen = tal_count(f->path);
pay_flows[i] = pf;
pf->payment = payment;
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);
for (size_t j = 0; j < plen; j++) {
struct gossmap_node *n;
n = gossmap_nth_node(gossmap, f->path[j], !f->dirs[j]);
gossmap_node_get_id(gossmap, n, &pf->path_nodes[j]);
pf->path_scids[j]
= gossmap_chan_scid(gossmap, f->path[j]);
}
/* Calculate cumulative delays (backwards) */
pf->cltv_delays = tal_arr(pf, u32, plen);
pf->cltv_delays[plen-1] = final_cltvs[i];
for (int j = (int)plen-2; j >= 0; j--) {
pf->cltv_delays[j] = pf->cltv_delays[j+1]
+ f->path[j]->half[f->dirs[j]].delay;
}
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);
return pay_flows;
}
static bitmap *make_disabled_bitmap(const tal_t *ctx,
const struct gossmap *gossmap,
const struct short_channel_id *scids)
{
bitmap *disabled
= tal_arrz(ctx, bitmap,
BITMAP_NWORDS(gossmap_max_chan_idx(gossmap)));
for (size_t i = 0; i < tal_count(scids); i++) {
struct gossmap_chan *c = gossmap_find_chan(gossmap, &scids[i]);
if (c)
bitmap_set_bit(disabled, gossmap_chan_idx(gossmap, c));
}
return disabled;
}
static u64 flows_worst_delay(struct flow **flows)
{
u64 maxdelay = 0;
for (size_t i = 0; i < tal_count(flows); i++) {
u64 delay = flow_delay(flows[i]);
if (delay > maxdelay)
maxdelay = delay;
}
return maxdelay;
}
/* FIXME: If only path has channels marked disabled, we should try... */
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++) {
const struct half_chan *h = &flow->path[i]->half[flow->dirs[i]];
struct short_channel_id scid;
const char *reason;
if (!h->enabled)
reason = "channel_update said it was disabled";
else if (amount_msat_greater_fp16(flow->amounts[i], h->htlc_max))
reason = "htlc above maximum";
else if (amount_msat_less_fp16(flow->amounts[i], h->htlc_min))
reason = "htlc below minimum";
else
continue;
scid = gossmap_chan_scid(gossmap, flow->path[i]);
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(&renepay->disabled, scid);
/* Add to existing bitmap */
bitmap_set_bit(disabled,
gossmap_chan_idx(gossmap, flow->path[i]));
disabled_some = true;
}
return disabled_some;
}
/* 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 renepay *renepay,
struct flow **flows,
const struct gossmap *gossmap,
bitmap *disabled)
{
bool disabled_some = false;
/* 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(renepay, flows[i],
gossmap,
disabled);
}
return disabled_some;
}
/* Get some payment flows to get this amount to destination, or NULL. */
struct pay_flow **get_payflows(struct renepay * renepay,
struct amount_msat amount,
struct amount_msat feebudget,
bool unlikely_ok,
bool is_entire_payment,
char const ** err_msg)
{
*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;
disabled = make_disabled_bitmap(tmpctx, pay_plugin->gossmap, renepay->disabled);
src = gossmap_find_node(pay_plugin->gossmap, &pay_plugin->my_id);
if (!src) {
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->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;
}
for (;;) {
struct flow **flows;
double prob;
struct amount_msat fee;
u64 delay;
bool too_unlikely, too_expensive, too_delayed;
const u32 *final_cltvs;
flows = minflow(tmpctx, pay_plugin->gossmap, src, dst,
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) {
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 = 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 < 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)
{
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) {
debug_paynote(p, "Flows too delayed, delay = %"PRIu64" (max %u)",
delay, p->maxdelay);
/* FIXME: What is a sane limit? */
if (p->delay_feefactor > 1000) {
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;
debug_paynote(p, "Doubling delay_feefactor to %f",
p->delay_feefactor);
continue; // retry
}
/* 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(renepay, flows, pay_plugin->gossmap,
disabled))
{
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,
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(renepay->payment, pay_plugin->gossmap,
flows, final_cltvs,
&renepay->next_partid);
break;
}
return pay_flows;
fail:
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;i<tal_count(flows);++i)
{
struct pay_flow *f = flows[i];
json_out_start(jout,NULL,'{');
json_out_add(jout,"success_prob",false,"%.2lf",f->success_prob);
json_out_start(jout,"path_scids",'[');
for(size_t j=0;j<tal_count(f->path_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;j<tal_count(f->path_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;j<tal_count(f->amounts);++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;j<tal_count(f->cltv_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;j<tal_count(f->path_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);
}

112
plugins/renepay/pay_flow.h Normal file
View File

@ -0,0 +1,112 @@
#ifndef LIGHTNING_PLUGINS_RENEPAY_PAY_FLOW_H
#define LIGHTNING_PLUGINS_RENEPAY_PAY_FLOW_H
#include "config.h"
#include <ccan/ccan/tal/str/str.h>
#include <ccan/short_types/short_types.h>
#include <common/utils.h>
#include <plugins/renepay/debug.h>
#include <plugins/renepay/flow.h>
#include <plugins/renepay/payment.h>
/* 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;
// TODO(eduardo): remove this, unnecessary
int attempt;
/* 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;
int *path_dirs;
/* CLTV delays for each hop */
u32 *cltv_delays;
/* The amounts at each step */
struct amount_msat *amounts;
/* Probability estimate (0-1) */
double success_prob;
};
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,
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 */

230
plugins/renepay/payment.c Normal file
View File

@ -0,0 +1,230 @@
#include "config.h"
#include <ccan/ccan/tal/str/str.h>
#include <plugins/renepay/debug.h>
#include <plugins/renepay/payment.h>
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(response, "amount_msat",
p->amount);
json_add_amount_msat(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;
}

163
plugins/renepay/payment.h Normal file
View File

@ -0,0 +1,163 @@
#ifndef LIGHTNING_PLUGINS_RENEPAY_PAYMENT_H
#define LIGHTNING_PLUGINS_RENEPAY_PAYMENT_H
#include "config.h"
#include <common/gossmap.h>
#include <plugins/libplugin.h>
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 */

View File

@ -0,0 +1,19 @@
# Note that these actually #include everything they need, except ccan/ and bitcoin/.
# That allows for unit testing of statics, and special effects.
PLUGIN_RENEPAY_TEST_SRC := $(wildcard plugins/renepay/test/run-*.c)
PLUGIN_RENEPAY_TEST_OBJS := $(PLUGIN_RENEPAY_TEST_SRC:.c=.o)
PLUGIN_RENEPAY_TEST_PROGRAMS := $(PLUGIN_RENEPAY_TEST_OBJS:.o=)
ALL_C_SOURCES += $(PLUGIN_RENEPAY_TEST_SRC)
ALL_TEST_PROGRAMS += $(PLUGIN_RENEPAY_TEST_PROGRAMS)
$(PLUGIN_RENEPAY_TEST_OBJS): $(PLUGIN_RENEPAY_SRC)
PLUGIN_RENEPAY_TEST_COMMON_OBJS := \
plugins/renepay/dijkstra.o \
plugins/renepay/debug.o
$(PLUGIN_RENEPAY_TEST_PROGRAMS): $(PLUGIN_RENEPAY_TEST_COMMON_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) $(CCAN_OBJS) bitcoin/chainparams.o common/gossmap.o common/fp16.o common/dijkstra.o common/bolt12.o common/bolt12_merkle.o wire/bolt12$(EXP)_wiregen.o
check-renepay: $(PLUGIN_RENEPAY_TEST_PROGRAMS:%=unittest/%)
check-units: check-renepay

View File

@ -0,0 +1,100 @@
#include "config.h"
#include <stdio.h>
#include <assert.h>
#include <common/wireaddr.h>
#include <common/bigsize.h>
#include <common/channel_id.h>
#include <common/setup.h>
#include <common/utils.h>
#include <plugins/renepay/dijkstra.h>
static void insertion_in_increasing_distance(const tal_t *ctx)
{
dijkstra_malloc(ctx,10);
for(int i=0;i<dijkstra_maxsize();++i)
{
dijkstra_update(i,10+i);
assert(dijkstra_size()==(i+1));
}
dijkstra_update(3,3);
assert(dijkstra_top()==3);
dijkstra_update(3,15);
assert(dijkstra_top()==0);
dijkstra_update(3,-1);
assert(dijkstra_top()==3);
dijkstra_pop();
assert(dijkstra_size()==9);
assert(dijkstra_top()==0);
// Insert again
dijkstra_update(3,3+10);
u32 top=0;
while(!dijkstra_empty())
{
assert(top==dijkstra_top());
top++;
dijkstra_pop();
}
}
static void insertion_in_decreasing_distance(const tal_t *ctx)
{
dijkstra_malloc(ctx,10);
for(int i=0;i<dijkstra_maxsize();++i)
{
dijkstra_update(i,10-i);
assert(dijkstra_size()==(i+1));
}
dijkstra_update(3,-3);
assert(dijkstra_top()==3);
dijkstra_update(3,15);
assert(dijkstra_top()==9);
dijkstra_update(3,-1);
assert(dijkstra_top()==3);
dijkstra_pop();
assert(dijkstra_size()==9);
assert(dijkstra_top()==9);
// Insert again
dijkstra_update(3,10-3);
u32 top=9;
while(!dijkstra_empty())
{
assert(top==dijkstra_top());
top--;
dijkstra_pop();
}
}
int main(int argc, char *argv[])
{
common_setup(argv[0]);
insertion_in_increasing_distance(NULL);
insertion_in_decreasing_distance(tmpctx);
// test dijkstra_free
dijkstra_free();
// we can call it twice, no problem
dijkstra_free();
// does tal_free() cleansup correctly?
const tal_t *this_ctx = tal(NULL,tal_t);
insertion_in_increasing_distance(this_ctx);
tal_free(this_ctx);
insertion_in_decreasing_distance(tmpctx);
common_shutdown();
}

View File

@ -0,0 +1,156 @@
#include "config.h"
#define RENEPAY_UNITTEST // logs are written in /tmp/debug.txt
#include "../payment.c"
#include "../flow.c"
#include "../uncertainty_network.c"
#include "../mcf.c"
#include <ccan/array_size/array_size.h>
#include <ccan/read_write_all/read_write_all.h>
#include <common/bigsize.h>
#include <common/channel_id.h>
#include <common/gossip_store.h>
#include <common/node_id.h>
#include <common/setup.h>
#include <common/type_to_string.h>
#include <common/wireaddr.h>
#include <stdio.h>
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]<delivered\n",
__PRETTY_FUNCTION__);
}
tal_append_fmt(&buff," prob %.2f, %s delivered with fee %s\n",
flows[i]->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", 5, &scid12));
assert(short_channel_id_from_str("1x3x0", 5, &scid13));
assert(short_channel_id_from_str("2x4x0", 5, &scid24));
assert(short_channel_id_from_str("3x4x0", 5, &scid34));
mods = gossmap_localmods_new(tmpctx);
/* 1->2->4 has capacity 10k sat, 1->3->4 has capacity 5k sat (lower fee!) */
assert(gossmap_local_addchan(mods, &l1, &l2, &scid12, NULL));
assert(gossmap_local_updatechan(mods, &scid12,
/*htlc_min=*/ AMOUNT_MSAT(0),
/*htlc_max=*/ AMOUNT_MSAT(10000000),
/*base_fee=*/ 0,
/*ppm_fee =*/ 1001,
/* delay =*/ 5,
/* enabled=*/ true,
/* dir =*/ 0));
assert(gossmap_local_addchan(mods, &l2, &l4, &scid24, NULL));
assert(gossmap_local_updatechan(mods, &scid24,
AMOUNT_MSAT(0),
AMOUNT_MSAT(10000000),
0, 1002, 5,
true,
0));
assert(gossmap_local_addchan(mods, &l1, &l3, &scid13, NULL));
assert(gossmap_local_updatechan(mods, &scid13,
AMOUNT_MSAT(0),
AMOUNT_MSAT(5000000),
0, 503, 5,
true,
0));
assert(gossmap_local_addchan(mods, &l3, &l4, &scid34, NULL));
assert(gossmap_local_updatechan(mods, &scid34,
AMOUNT_MSAT(0),
AMOUNT_MSAT(5000000),
0, 504, 5,
true,
0));
gossmap_apply_localmods(gossmap, mods);
chan_extra_map = tal(tmpctx, struct chan_extra_map);
chan_extra_map_init(chan_extra_map);
/* The local chans have no "capacity", so set them manually. */
new_chan_extra(chan_extra_map,
scid12,
AMOUNT_MSAT(10000000));
new_chan_extra(chan_extra_map,
scid24,
AMOUNT_MSAT(10000000));
new_chan_extra(chan_extra_map,
scid13,
AMOUNT_MSAT(5000000));
new_chan_extra(chan_extra_map,
scid34,
AMOUNT_MSAT(5000000));
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();
}

View File

@ -0,0 +1,470 @@
#include "config.h"
#define RENEPAY_UNITTEST // logs are written in /tmp/debug.txt
#include "../payment.c"
#include "../flow.c"
#include "../uncertainty_network.c"
#include "../mcf.c"
#include <ccan/array_size/array_size.h>
#include <ccan/read_write_all/read_write_all.h>
#include <common/bigsize.h>
#include <common/channel_id.h>
#include <common/node_id.h>
#include <common/setup.h>
#include <common/type_to_string.h>
#include <common/wireaddr.h>
#include <inttypes.h>
#include <stdio.h>
/* 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
*/
static u8 canned_map[] = {
0x09, 0x80, 0x00, 0x01, 0xbc, 0x09, 0x8b, 0x67, 0xe6, 0x00, 0x00, 0x00, 0x00, 0x10, 0x08, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x42, 0x40, 0x01, 0xb0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x22, 0x6e
, 0x46, 0x11, 0x1a, 0x0b, 0x59, 0xca, 0xaf, 0x12, 0x60, 0x43, 0xeb, 0x5b, 0xbf, 0x28, 0xc3, 0x4f
, 0x3a, 0x5e, 0x33, 0x2a, 0x1f, 0xc7, 0xb2, 0xb7, 0x3c, 0xf1, 0x88, 0x91, 0x0f, 0x00, 0x00, 0x67
, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x2d, 0x22, 0x36, 0x20, 0xa3, 0x59, 0xa4, 0x7f, 0xf7, 0xf7
, 0xac, 0x44, 0x7c, 0x85, 0xc4, 0x6c, 0x92, 0x3d, 0xa5, 0x33, 0x89, 0x22, 0x1a, 0x00, 0x54, 0xc1
, 0x1c, 0x1e, 0x3c, 0xa3, 0x1d, 0x59, 0x03, 0x5d, 0x2b, 0x11, 0x92, 0xdf, 0xba, 0x13, 0x4e, 0x10
, 0xe5, 0x40, 0x87, 0x5d, 0x36, 0x6e, 0xbc, 0x8b, 0xc3, 0x53, 0xd5, 0xaa, 0x76, 0x6b, 0x80, 0xc0
, 0x90, 0xb3, 0x9c, 0x3a, 0x5d, 0x88, 0x5d, 0x03, 0x1b, 0x84, 0xc5, 0x56, 0x7b, 0x12, 0x64, 0x40
, 0x99, 0x5d, 0x3e, 0xd5, 0xaa, 0xba, 0x05, 0x65, 0xd7, 0x1e, 0x18, 0x34, 0x60, 0x48, 0x19, 0xff
, 0x9c, 0x17, 0xf5, 0xe9, 0xd5, 0xdd, 0x07, 0x8f, 0x03, 0x1b, 0x84, 0xc5, 0x56, 0x7b, 0x12, 0x64
, 0x40, 0x99, 0x5d, 0x3e, 0xd5, 0xaa, 0xba, 0x05, 0x65, 0xd7, 0x1e, 0x18, 0x34, 0x60, 0x48, 0x19
, 0xff, 0x9c, 0x17, 0xf5, 0xe9, 0xd5, 0xdd, 0x07, 0x8f, 0x80, 0x00, 0x00, 0x8e, 0x33, 0x3b, 0x90
, 0x12, 0x00, 0x00, 0x00, 0x00, 0x10, 0x06, 0x00, 0x8a, 0x01, 0x02, 0x14, 0xb8, 0x21, 0x42, 0x7d
, 0x40, 0x89, 0x60, 0x71, 0x05, 0x8d, 0xe4, 0x50, 0x8e, 0xc3, 0x87, 0x6f, 0xa6, 0x4b, 0x19, 0xe4
, 0x81, 0xc5, 0x5f, 0xb7, 0x04, 0xb8, 0x74, 0x08, 0x0b, 0x40, 0x5a, 0x74, 0x89, 0xbc, 0x63, 0x24
, 0x27, 0x93, 0x4d, 0xfc, 0x1a, 0x72, 0xe4, 0xc7, 0xf8, 0x9b, 0xc1, 0x6b, 0xad, 0x9b, 0x04, 0x2e
, 0x14, 0xa4, 0xe9, 0xf5, 0x80, 0xf1, 0x02, 0x8f, 0x50, 0xf3, 0x2c, 0x06, 0x22, 0x6e, 0x46, 0x11
, 0x1a, 0x0b, 0x59, 0xca, 0xaf, 0x12, 0x60, 0x43, 0xeb, 0x5b, 0xbf, 0x28, 0xc3, 0x4f, 0x3a, 0x5e
, 0x33, 0x2a, 0x1f, 0xc7, 0xb2, 0xb7, 0x3c, 0xf1, 0x88, 0x91, 0x0f, 0x00, 0x00, 0x67, 0x00, 0x00
, 0x01, 0x00, 0x00, 0x60, 0x17, 0x53, 0x70, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x3b
, 0x02, 0x33, 0x80, 0x80, 0x00, 0x00, 0x8e, 0x3e, 0xa2, 0x81, 0xe6, 0x00, 0x00, 0x00, 0x00, 0x10
, 0x06, 0x00, 0x8a, 0x01, 0x02, 0x01, 0x0a, 0xb3, 0x54, 0x3f, 0xd2, 0xa9, 0xf5, 0x30, 0x0f, 0x60
, 0x7d, 0xf9, 0xf1, 0xdd, 0x63, 0x62, 0xd8, 0xde, 0xe2, 0x94, 0xe4, 0x68, 0xc9, 0x5c, 0xe8, 0x32
, 0x9b, 0x14, 0xd9, 0xf8, 0x6a, 0x23, 0x3a, 0x67, 0x10, 0x09, 0x64, 0x96, 0x40, 0xcb, 0x0b, 0xf5
, 0xec, 0xe6, 0xba, 0x8e, 0x77, 0xb4, 0x6a, 0xf1, 0x39, 0x94, 0x86, 0xb0, 0x69, 0xd5, 0x17, 0x67
, 0x83, 0xda, 0xfa, 0x49, 0x63, 0x06, 0x22, 0x6e, 0x46, 0x11, 0x1a, 0x0b, 0x59, 0xca, 0xaf, 0x12
, 0x60, 0x43, 0xeb, 0x5b, 0xbf, 0x28, 0xc3, 0x4f, 0x3a, 0x5e, 0x33, 0x2a, 0x1f, 0xc7, 0xb2, 0xb7
, 0x3c, 0xf1, 0x88, 0x91, 0x0f, 0x00, 0x00, 0x67, 0x00, 0x00, 0x01, 0x00, 0x00, 0x60, 0x17, 0x53
, 0x70, 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
, 0x0a, 0x01, 0xf0, 0xcb, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x10, 0x07, 0x00, 0x00, 0x67, 0x00, 0x00
, 0x01, 0x00, 0x00, 0x40, 0x00, 0x01, 0xb0, 0xd2, 0xfa, 0x8f, 0x8d, 0x60, 0x17, 0x53, 0x70, 0x01
, 0x00, 0x24, 0xfd, 0xae, 0x1a, 0xc8, 0x40, 0xa7, 0x33, 0x22, 0xe1, 0x45, 0x7e, 0x76, 0xb8, 0x86
, 0xdd, 0x17, 0x8c, 0xd4, 0x49, 0x4b, 0x14, 0x3f, 0x81, 0xd4, 0xd4, 0xfa, 0xa7, 0x16, 0x17, 0xd2
, 0x51, 0x33, 0x9e, 0xcb, 0x0e, 0x22, 0x1c, 0xf6, 0x02, 0x3a, 0x2e, 0x3e, 0x94, 0xf8, 0xae, 0xdb
, 0xee, 0x47, 0x23, 0xda, 0x5c, 0x35, 0x51, 0x57, 0xd8, 0xe4, 0x67, 0x2b, 0x46, 0x82, 0x5e, 0xc7
, 0x98, 0x51, 0xb3, 0xb0, 0x1a, 0x2c, 0x72, 0x3f, 0x9b, 0xf5, 0xdb, 0xa8, 0xe3, 0x5f, 0x8b, 0x47
, 0x9d, 0x9c, 0xd9, 0x73, 0xae, 0xc5, 0x0c, 0xca, 0x08, 0xfb, 0x97, 0x57, 0xb5, 0x21, 0x92, 0x05
, 0x18, 0x42, 0x2d, 0x68, 0x19, 0x70, 0x76, 0x30, 0x61, 0x24, 0xff, 0xa5, 0xb6, 0x58, 0xa2, 0xe2
, 0xb3, 0x68, 0x93, 0x37, 0xda, 0x6c, 0x3c, 0xcc, 0x5e, 0xf7, 0x3b, 0x51, 0x29, 0x64, 0x30, 0xbe
, 0x2a, 0x19, 0x38, 0x88, 0x9d, 0xda, 0x2a, 0xd1, 0xcb, 0x5e, 0x33, 0xdb, 0x75, 0xcf, 0x2e, 0x0e
, 0xfd, 0xbd, 0x38, 0xce, 0x01, 0x54, 0x62, 0x30, 0xb4, 0xdd, 0xdc, 0x7f, 0x67, 0xca, 0xf8, 0x39
, 0x10, 0x02, 0x8a, 0x05, 0x3b, 0x76, 0x62, 0x72, 0xd2, 0x84, 0x71, 0x19, 0x19, 0x30, 0x92, 0xfa
, 0x2a, 0x1f, 0xdf, 0x71, 0xe3, 0xd8, 0x4a, 0x56, 0xd0, 0xe4, 0x35, 0xfe, 0x5d, 0x4a, 0x5b, 0x5b
, 0x90, 0x05, 0x28, 0xe4, 0x3b, 0x24, 0x13, 0x46, 0x99, 0x45, 0xc4, 0x92, 0x14, 0x7d, 0x43, 0x21
, 0x06, 0x50, 0x51, 0xf8, 0x5b, 0x92, 0xb5, 0xb0, 0x90, 0xb1, 0xd7, 0x0d, 0x5a, 0xac, 0xfe, 0xf4
, 0xe2, 0x70, 0x3e, 0x97, 0x42, 0x25, 0xfb, 0x21, 0x15, 0xf6, 0xb9, 0x32, 0xc8, 0xc3, 0x03, 0xbd
, 0x7a, 0xbd, 0x86, 0xf7, 0xcd, 0x64, 0xe6, 0x1a, 0x7f, 0x5a, 0x04, 0x7a, 0x22, 0xad, 0x7c, 0xfc
, 0x6a, 0x00, 0x00, 0x06, 0x22, 0x6e, 0x46, 0x11, 0x1a, 0x0b, 0x59, 0xca, 0xaf, 0x12, 0x60, 0x43
, 0xeb, 0x5b, 0xbf, 0x28, 0xc3, 0x4f, 0x3a, 0x5e, 0x33, 0x2a, 0x1f, 0xc7, 0xb2, 0xb7, 0x3c, 0xf1
, 0x88, 0x91, 0x0f, 0x00, 0x00, 0x67, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x2d, 0x22, 0x36, 0x20
, 0xa3, 0x59, 0xa4, 0x7f, 0xf7, 0xf7, 0xac, 0x44, 0x7c, 0x85, 0xc4, 0x6c, 0x92, 0x3d, 0xa5, 0x33
, 0x89, 0x22, 0x1a, 0x00, 0x54, 0xc1, 0x1c, 0x1e, 0x3c, 0xa3, 0x1d, 0x59, 0x03, 0x5d, 0x2b, 0x11
, 0x92, 0xdf, 0xba, 0x13, 0x4e, 0x10, 0xe5, 0x40, 0x87, 0x5d, 0x36, 0x6e, 0xbc, 0x8b, 0xc3, 0x53
, 0xd5, 0xaa, 0x76, 0x6b, 0x80, 0xc0, 0x90, 0xb3, 0x9c, 0x3a, 0x5d, 0x88, 0x5d, 0x02, 0xd5, 0x95
, 0xae, 0x92, 0xb3, 0x54, 0x4c, 0x32, 0x50, 0xfb, 0x77, 0x2f, 0x21, 0x4a, 0xd8, 0xd4, 0xc5, 0x14
, 0x25, 0x03, 0x37, 0x40, 0xa5, 0xbc, 0xc3, 0x57, 0x19, 0x0a, 0xdd, 0x6d, 0x7e, 0x7a, 0x02, 0xd6
, 0x06, 0x3d, 0x02, 0x26, 0x91, 0xb2, 0x49, 0x0a, 0xb4, 0x54, 0xde, 0xe7, 0x3a, 0x57, 0xc6, 0xff
, 0x5d, 0x30, 0x83, 0x52, 0xb4, 0x61, 0xec, 0xe6, 0x9f, 0x3c, 0x28, 0x4f, 0x2c, 0x24, 0x12, 0x00
, 0x00, 0x00, 0x0a, 0x91, 0x11, 0x83, 0xf6, 0x00, 0x00, 0x00, 0x00, 0x10, 0x05, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x0f, 0x42, 0x40, 0xc0, 0x00, 0x00, 0x8a, 0xf3, 0x48, 0xd5, 0xb3, 0x60, 0x17, 0x53
, 0x70, 0x01, 0x02, 0x14, 0xb8, 0x21, 0x42, 0x7d, 0x40, 0x89, 0x60, 0x71, 0x05, 0x8d, 0xe4, 0x50
, 0x8e, 0xc3, 0x87, 0x6f, 0xa6, 0x4b, 0x19, 0xe4, 0x81, 0xc5, 0x5f, 0xb7, 0x04, 0xb8, 0x74, 0x08
, 0x0b, 0x40, 0x5a, 0x74, 0x89, 0xbc, 0x63, 0x24, 0x27, 0x93, 0x4d, 0xfc, 0x1a, 0x72, 0xe4, 0xc7
, 0xf8, 0x9b, 0xc1, 0x6b, 0xad, 0x9b, 0x04, 0x2e, 0x14, 0xa4, 0xe9, 0xf5, 0x80, 0xf1, 0x02, 0x8f
, 0x50, 0xf3, 0x2c, 0x06, 0x22, 0x6e, 0x46, 0x11, 0x1a, 0x0b, 0x59, 0xca, 0xaf, 0x12, 0x60, 0x43
, 0xeb, 0x5b, 0xbf, 0x28, 0xc3, 0x4f, 0x3a, 0x5e, 0x33, 0x2a, 0x1f, 0xc7, 0xb2, 0xb7, 0x3c, 0xf1
, 0x88, 0x91, 0x0f, 0x00, 0x00, 0x67, 0x00, 0x00, 0x01, 0x00, 0x00, 0x60, 0x17, 0x53, 0x70, 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, 0xc0, 0x00, 0x00, 0x8a, 0xfe
, 0xd1, 0xc4, 0x47, 0x60, 0x17, 0x53, 0x70, 0x01, 0x02, 0x01, 0x0a, 0xb3, 0x54, 0x3f, 0xd2, 0xa9
, 0xf5, 0x30, 0x0f, 0x60, 0x7d, 0xf9, 0xf1, 0xdd, 0x63, 0x62, 0xd8, 0xde, 0xe2, 0x94, 0xe4, 0x68
, 0xc9, 0x5c, 0xe8, 0x32, 0x9b, 0x14, 0xd9, 0xf8, 0x6a, 0x23, 0x3a, 0x67, 0x10, 0x09, 0x64, 0x96
, 0x40, 0xcb, 0x0b, 0xf5, 0xec, 0xe6, 0xba, 0x8e, 0x77, 0xb4, 0x6a, 0xf1, 0x39, 0x94, 0x86, 0xb0
, 0x69, 0xd5, 0x17, 0x67, 0x83, 0xda, 0xfa, 0x49, 0x63, 0x06, 0x22, 0x6e, 0x46, 0x11, 0x1a, 0x0b
, 0x59, 0xca, 0xaf, 0x12, 0x60, 0x43, 0xeb, 0x5b, 0xbf, 0x28, 0xc3, 0x4f, 0x3a, 0x5e, 0x33, 0x2a
, 0x1f, 0xc7, 0xb2, 0xb7, 0x3c, 0xf1, 0x88, 0x91, 0x0f, 0x00, 0x00, 0x67, 0x00, 0x00, 0x01, 0x00
, 0x00, 0x60, 0x17, 0x53, 0x70, 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, 0x40, 0x00, 0x00, 0x9b, 0x4f, 0x9d, 0xb7, 0xb9, 0x60, 0x17, 0x53, 0x77, 0x01, 0x01, 0x6e
, 0x99, 0xf5, 0x9c, 0x1f, 0x21, 0x8d, 0x4a, 0x2b, 0x6e, 0x36, 0x9a, 0x95, 0x20, 0x76, 0x2c, 0x27
, 0xfb, 0xa8, 0xb1, 0x82, 0x1f, 0x64, 0x34, 0x93, 0x91, 0x9c, 0xeb, 0xfa, 0x40, 0x50, 0x73, 0x4d
, 0x00, 0xce, 0x10, 0xbf, 0x3f, 0x42, 0x3e, 0x56, 0x8f, 0xf8, 0xe0, 0x59, 0x58, 0xb5, 0xbd, 0xc5
, 0x00, 0x82, 0xe3, 0x27, 0x92, 0x5b, 0xf8, 0x4f, 0x2c, 0x39, 0xec, 0x49, 0x3b, 0x07, 0x5e, 0x00
, 0x0d, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x22, 0xaa, 0xa2, 0x60, 0x17
, 0x53, 0x77, 0x02, 0x2d, 0x22, 0x36, 0x20, 0xa3, 0x59, 0xa4, 0x7f, 0xf7, 0xf7, 0xac, 0x44, 0x7c
, 0x85, 0xc4, 0x6c, 0x92, 0x3d, 0xa5, 0x33, 0x89, 0x22, 0x1a, 0x00, 0x54, 0xc1, 0x1c, 0x1e, 0x3c
, 0xa3, 0x1d, 0x59, 0x02, 0x2d, 0x22, 0x53, 0x49, 0x4c, 0x45, 0x4e, 0x54, 0x41, 0x52, 0x54, 0x49
, 0x53, 0x54, 0x2d, 0x2d, 0x35, 0x36, 0x2d, 0x67, 0x64, 0x64, 0x31, 0x35, 0x33, 0x63, 0x38, 0x2d
, 0x6d, 0x6f, 0x64, 0x64, 0x65, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9b, 0x15, 0x33, 0x0c, 0x6b
, 0x60, 0x17, 0x53, 0x77, 0x01, 0x01, 0x0e, 0x07, 0xaf, 0xd2, 0x33, 0x19, 0x0e, 0x06, 0x01, 0x6d
, 0x57, 0x88, 0x4e, 0x66, 0xf8, 0x08, 0xd9, 0x65, 0x8a, 0x73, 0xfb, 0x1d, 0xe0, 0xad, 0xee, 0x47
, 0xf8, 0x1c, 0xfc, 0xc3, 0xd2, 0xfd, 0x06, 0x3e, 0x5a, 0x05, 0x65, 0x72, 0x18, 0x61, 0xb8, 0x23
, 0x04, 0x3d, 0x4b, 0x39, 0x79, 0xe0, 0x85, 0x38, 0xd2, 0x92, 0x14, 0x35, 0x32, 0xaa, 0x9f, 0xab
, 0x5f, 0x98, 0x2c, 0x53, 0xfe, 0x0d, 0x00, 0x0d, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00
, 0x00, 0x00, 0x22, 0xaa, 0xa2, 0x60, 0x17, 0x53, 0x77, 0x03, 0x5d, 0x2b, 0x11, 0x92, 0xdf, 0xba
, 0x13, 0x4e, 0x10, 0xe5, 0x40, 0x87, 0x5d, 0x36, 0x6e, 0xbc, 0x8b, 0xc3, 0x53, 0xd5, 0xaa, 0x76
, 0x6b, 0x80, 0xc0, 0x90, 0xb3, 0x9c, 0x3a, 0x5d, 0x88, 0x5d, 0x03, 0x5d, 0x2b, 0x48, 0x4f, 0x50
, 0x50, 0x49, 0x4e, 0x47, 0x46, 0x49, 0x52, 0x45, 0x2d, 0x33, 0x2d, 0x35, 0x36, 0x2d, 0x67, 0x64
, 0x64, 0x31, 0x35, 0x33, 0x63, 0x38, 0x2d, 0x6d, 0x6f, 0x64, 0x64, 0x65, 0x64, 0x00, 0x00, 0x40
, 0x00, 0x00, 0x8a, 0x22, 0x55, 0xdf, 0xb2, 0x60, 0x17, 0x53, 0x79, 0x01, 0x02, 0x3a, 0x2b, 0xe5
, 0x81, 0x83, 0xa3, 0x1a, 0x49, 0x93, 0x89, 0x8d, 0xac, 0xa7, 0xb2, 0x2e, 0xc3, 0x94, 0x6c, 0xd1
, 0xd6, 0xd0, 0x82, 0x34, 0xf3, 0x9c, 0x71, 0xa0, 0xd1, 0xdb, 0x3f, 0xcc, 0xfc, 0x53, 0xce, 0x8c
, 0x84, 0x3d, 0x14, 0x2c, 0x81, 0x4a, 0x07, 0xf0, 0x00, 0x03, 0x7a, 0x28, 0x10, 0xf4, 0xb9, 0x50
, 0xb3, 0x22, 0x00, 0xdf, 0xc2, 0xc7, 0xfb, 0x6f, 0xf3, 0xfb, 0xf6, 0x94, 0x8e, 0x06, 0x22, 0x6e
, 0x46, 0x11, 0x1a, 0x0b, 0x59, 0xca, 0xaf, 0x12, 0x60, 0x43, 0xeb, 0x5b, 0xbf, 0x28, 0xc3, 0x4f
, 0x3a, 0x5e, 0x33, 0x2a, 0x1f, 0xc7, 0xb2, 0xb7, 0x3c, 0xf1, 0x88, 0x91, 0x0f, 0x00, 0x00, 0x67
, 0x00, 0x00, 0x01, 0x00, 0x00, 0x60, 0x17, 0x53, 0x79, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x03, 0xe8, 0x00, 0x00, 0x00
, 0x00, 0x3b, 0x02, 0x33, 0x80, 0x00, 0x00, 0x01, 0xbc, 0x4d, 0x34, 0xb9, 0xcd, 0x00, 0x00, 0x00
, 0x00, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x42, 0x40, 0x01, 0xb0, 0x01, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x06, 0x22, 0x6e, 0x46, 0x11, 0x1a, 0x0b, 0x59, 0xca, 0xaf, 0x12, 0x60, 0x43, 0xeb, 0x5b
, 0xbf, 0x28, 0xc3, 0x4f, 0x3a, 0x5e, 0x33, 0x2a, 0x1f, 0xc7, 0xb2, 0xb7, 0x3c, 0xf1, 0x88, 0x91
, 0x0f, 0x00, 0x00, 0x6e, 0x00, 0x00, 0x01, 0x00, 0x01, 0x02, 0x2d, 0x22, 0x36, 0x20, 0xa3, 0x59
, 0xa4, 0x7f, 0xf7, 0xf7, 0xac, 0x44, 0x7c, 0x85, 0xc4, 0x6c, 0x92, 0x3d, 0xa5, 0x33, 0x89, 0x22
, 0x1a, 0x00, 0x54, 0xc1, 0x1c, 0x1e, 0x3c, 0xa3, 0x1d, 0x59, 0x02, 0x66, 0xe4, 0x59, 0x8d, 0x1d
, 0x3c, 0x41, 0x5f, 0x57, 0x2a, 0x84, 0x88, 0x83, 0x0b, 0x60, 0xf7, 0xe7, 0x44, 0xed, 0x92, 0x35
, 0xeb, 0x0b, 0x1b, 0xa9, 0x32, 0x83, 0xb3, 0x15, 0xc0, 0x35, 0x18, 0x03, 0x1b, 0x84, 0xc5, 0x56
, 0x7b, 0x12, 0x64, 0x40, 0x99, 0x5d, 0x3e, 0xd5, 0xaa, 0xba, 0x05, 0x65, 0xd7, 0x1e, 0x18, 0x34
, 0x60, 0x48, 0x19, 0xff, 0x9c, 0x17, 0xf5, 0xe9, 0xd5, 0xdd, 0x07, 0x8f, 0x03, 0x1b, 0x84, 0xc5
, 0x56, 0x7b, 0x12, 0x64, 0x40, 0x99, 0x5d, 0x3e, 0xd5, 0xaa, 0xba, 0x05, 0x65, 0xd7, 0x1e, 0x18
, 0x34, 0x60, 0x48, 0x19, 0xff, 0x9c, 0x17, 0xf5, 0xe9, 0xd5, 0xdd, 0x07, 0x8f, 0x80, 0x00, 0x00
, 0x8e, 0xf4, 0xbc, 0x2c, 0x78, 0x00, 0x00, 0x00, 0x00, 0x10, 0x06, 0x00, 0x8a, 0x01, 0x02, 0x7a
, 0x2a, 0x3b, 0xad, 0x69, 0xf3, 0x8b, 0xba, 0xd2, 0xd3, 0xa2, 0x99, 0x66, 0x5f, 0x2d, 0x14, 0xc2
, 0xca, 0xc2, 0xf4, 0x84, 0x97, 0x21, 0x93, 0x2f, 0xfd, 0x44, 0x19, 0xf6, 0xfa, 0x7f, 0x21, 0x3c
, 0x61, 0x45, 0x1e, 0x67, 0xfd, 0x5f, 0x9e, 0xee, 0x35, 0x03, 0xda, 0x96, 0xc3, 0x37, 0x2b, 0xfd
, 0x99, 0xb4, 0xdb, 0x0b, 0x6e, 0xa3, 0xdc, 0x8e, 0xad, 0x64, 0xf5, 0x9a, 0x4f, 0x5f, 0xae, 0x06
, 0x22, 0x6e, 0x46, 0x11, 0x1a, 0x0b, 0x59, 0xca, 0xaf, 0x12, 0x60, 0x43, 0xeb, 0x5b, 0xbf, 0x28
, 0xc3, 0x4f, 0x3a, 0x5e, 0x33, 0x2a, 0x1f, 0xc7, 0xb2, 0xb7, 0x3c, 0xf1, 0x88, 0x91, 0x0f, 0x00
, 0x00, 0x6e, 0x00, 0x00, 0x01, 0x00, 0x01, 0x60, 0x17, 0x53, 0x7a, 0x01, 0x00, 0x00, 0x06, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00
, 0x00, 0x00, 0x00, 0x3b, 0x02, 0x33, 0x80, 0x80, 0x00, 0x00, 0x8e, 0xc3, 0xd8, 0xfd, 0x83, 0x00
, 0x00, 0x00, 0x00, 0x10, 0x06, 0x00, 0x8a, 0x01, 0x02, 0x17, 0xdf, 0xc0, 0xb6, 0x5f, 0x8f, 0x42
, 0x50, 0xe1, 0x4d, 0x35, 0xe7, 0x57, 0x2a, 0x07, 0x66, 0x8e, 0xa9, 0xe2, 0x61, 0xbf, 0xbc, 0x91
, 0x5c, 0xa1, 0x80, 0x43, 0xcf, 0xb2, 0xba, 0x40, 0xf6, 0x2f, 0x0d, 0x37, 0x2c, 0xbc, 0x90, 0x96
, 0x71, 0x00, 0x79, 0x35, 0xe3, 0xe8, 0x94, 0x90, 0x3c, 0x23, 0x8f, 0x5b, 0x8e, 0xcc, 0x39, 0x82
, 0x2e, 0xdf, 0xbc, 0xcb, 0x66, 0xe9, 0xe4, 0x3e, 0xad, 0x06, 0x22, 0x6e, 0x46, 0x11, 0x1a, 0x0b
, 0x59, 0xca, 0xaf, 0x12, 0x60, 0x43, 0xeb, 0x5b, 0xbf, 0x28, 0xc3, 0x4f, 0x3a, 0x5e, 0x33, 0x2a
, 0x1f, 0xc7, 0xb2, 0xb7, 0x3c, 0xf1, 0x88, 0x91, 0x0f, 0x00, 0x00, 0x6e, 0x00, 0x00, 0x01, 0x00
, 0x01, 0x60, 0x17, 0x53, 0x7a, 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, 0x40, 0x00, 0x00, 0x8a, 0x62, 0xdd, 0xe7, 0xfd, 0x60, 0x17, 0x53, 0x7b, 0x01, 0x02, 0x0b
, 0x5d, 0x1b, 0x41, 0x29, 0x50, 0xe7, 0x79, 0x39, 0x76, 0xc2, 0xd0, 0xbd, 0x54, 0x2c, 0x1c, 0x2b
, 0x78, 0x25, 0x8b, 0xd6, 0x2d, 0x70, 0x09, 0x73, 0xb7, 0x1c, 0xe4, 0xa2, 0x88, 0x98, 0xb6, 0x44
, 0xa5, 0x33, 0x0a, 0x98, 0xdc, 0x63, 0xd1, 0x7b, 0x99, 0x49, 0xf2, 0x29, 0xe6, 0x6f, 0x58, 0xc6
, 0xcb, 0x5a, 0x74, 0xa0, 0xdf, 0xa7, 0x74, 0x84, 0xd5, 0xe1, 0x0f, 0x03, 0x7d, 0xb6, 0xcd, 0x06
, 0x22, 0x6e, 0x46, 0x11, 0x1a, 0x0b, 0x59, 0xca, 0xaf, 0x12, 0x60, 0x43, 0xeb, 0x5b, 0xbf, 0x28
, 0xc3, 0x4f, 0x3a, 0x5e, 0x33, 0x2a, 0x1f, 0xc7, 0xb2, 0xb7, 0x3c, 0xf1, 0x88, 0x91, 0x0f, 0x00
, 0x00, 0x67, 0x00, 0x00, 0x01, 0x00, 0x00, 0x60, 0x17, 0x53, 0x7b, 0x01, 0x01, 0x00, 0x06, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x03, 0xe8, 0x00
, 0x00, 0x00, 0x00, 0x3b, 0x02, 0x33, 0x80, 0x00, 0x00, 0x00, 0x8e, 0x76, 0xce, 0x94, 0x8e, 0x00
, 0x00, 0x00, 0x00, 0x10, 0x06, 0x00, 0x8a, 0x01, 0x02, 0x25, 0x9f, 0x23, 0x6a, 0xbd, 0x5b, 0x6a
, 0x6b, 0x0f, 0x77, 0xaa, 0xce, 0xe9, 0xe1, 0x6d, 0xe3, 0xfb, 0xcd, 0x10, 0xa6, 0x2b, 0xb6, 0x15
, 0x0c, 0xdf, 0xa1, 0xde, 0x79, 0x82, 0x99, 0xb1, 0x83, 0x47, 0x44, 0xf7, 0x20, 0xbc, 0x49, 0x11
, 0x59, 0x58, 0x25, 0x63, 0x76, 0x01, 0x69, 0x27, 0xdc, 0xb3, 0x6c, 0x68, 0xc8, 0x5f, 0xae, 0x13
, 0xaa, 0x46, 0xcc, 0xe9, 0x68, 0x03, 0x2a, 0xd3, 0x21, 0x06, 0x22, 0x6e, 0x46, 0x11, 0x1a, 0x0b
, 0x59, 0xca, 0xaf, 0x12, 0x60, 0x43, 0xeb, 0x5b, 0xbf, 0x28, 0xc3, 0x4f, 0x3a, 0x5e, 0x33, 0x2a
, 0x1f, 0xc7, 0xb2, 0xb7, 0x3c, 0xf1, 0x88, 0x91, 0x0f, 0x00, 0x00, 0x6e, 0x00, 0x00, 0x01, 0x00
, 0x01, 0x60, 0x17, 0x53, 0x7f, 0x01, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x03, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x3b, 0x02, 0x33
, 0x80, 0x00, 0x00, 0x00, 0x8e, 0x4b, 0x8b, 0x0b, 0xf9, 0x00, 0x00, 0x00, 0x00, 0x10, 0x06, 0x00
, 0x8a, 0x01, 0x02, 0x7b, 0xd9, 0xa5, 0xe6, 0xfb, 0x26, 0xe2, 0xe1, 0xcb, 0x9a, 0x68, 0xdf, 0x50
, 0x6c, 0x14, 0xcb, 0x5a, 0x2d, 0x12, 0x40, 0x94, 0x5e, 0xa4, 0x2d, 0xe9, 0x2a, 0x29, 0x48, 0xd5
, 0xd0, 0x2e, 0xd9, 0x0c, 0xdc, 0xba, 0xe2, 0x74, 0x6e, 0xfb, 0xca, 0x77, 0xea, 0xe9, 0xa2, 0xce
, 0x9a, 0xa8, 0x42, 0x09, 0xa3, 0xa3, 0xae, 0x0e, 0x0f, 0xcc, 0xd3, 0x93, 0xd5, 0xcc, 0x38, 0x76
, 0xd3, 0x58, 0xcc, 0x06, 0x22, 0x6e, 0x46, 0x11, 0x1a, 0x0b, 0x59, 0xca, 0xaf, 0x12, 0x60, 0x43
, 0xeb, 0x5b, 0xbf, 0x28, 0xc3, 0x4f, 0x3a, 0x5e, 0x33, 0x2a, 0x1f, 0xc7, 0xb2, 0xb7, 0x3c, 0xf1
, 0x88, 0x91, 0x0f, 0x00, 0x00, 0x6e, 0x00, 0x00, 0x01, 0x00, 0x01, 0x60, 0x17, 0x53, 0x7f, 0x01
, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00
, 0x00, 0x03, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x3b, 0x02, 0x33, 0x80
};
/* not_mcf sets NDEBUG, so assert() is useless */
#define ASSERT(x) do { if (!(x)) abort(); } while(0)
static const char *print_flows(
const tal_t *ctx,
const char *desc,
const struct gossmap *gossmap,
struct flow **flows)
{
tal_t *this_ctx = tal(ctx,tal_t);
char *buff = tal_fmt(ctx,"%s: %zu subflows\n", desc, tal_count(flows));
for (size_t i = 0; i < tal_count(flows); i++) {
struct amount_msat fee, delivered;
tal_append_fmt(&buff," ");
for (size_t j = 0; j < tal_count(flows[i]->path); j++) {
struct short_channel_id scid
= gossmap_chan_scid(gossmap,
flows[i]->path[j]);
tal_append_fmt(&buff,"%s%s", j ? "->" : "",
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();
tal_append_fmt(&buff," prob %.2f, %s delivered with fee %s\n",
flows[i]->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;
struct flow **flows;
struct short_channel_id scid12, scid23;
struct chan_extra_map *chan_extra_map;
common_setup(argv[0]);
fd = tmpdir_mkstemp(tmpctx, "run-not_mcf.XXXXXX", &gossfile);
assert(write_all(fd, canned_map, sizeof(canned_map)));
gossmap = gossmap_load(tmpctx, gossfile, NULL);
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));
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),
/* 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)));
/* fee_base_msat == 20, fee_proportional_millionths == 1000 */
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);
/* Should have filled in some extra data! */
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)));
ce = chan_extra_map_get(chan_extra_map, 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 */
struct short_channel_id scid13;
struct gossmap_localmods *mods = gossmap_localmods_new(tmpctx);
assert(short_channel_id_from_str("111x1x1", 7, &scid13));
/* 400,000sat channel from 1->3, basefee 0, ppm 1000, delay 5 */
assert(gossmap_local_addchan(mods, &l1, &l3, &scid13, NULL));
assert(gossmap_local_updatechan(mods, &scid13,
AMOUNT_MSAT(0),
AMOUNT_MSAT(400000000),
0, 1000, 5,
true,
0));
/* Apply changes, check they work. */
gossmap_apply_localmods(gossmap, mods);
struct gossmap_chan *local_chan = gossmap_find_chan(gossmap, &scid13);
assert(local_chan);
/* Clear that */
remove_completed_flow_set(gossmap, chan_extra_map, flows);
/* The local chans have no "capacity", so set it manually. */
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),
// /* 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)<tal_count(flows[1]->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! */
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 = */ 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
+ flows2[1]->amounts[1].millisatoshis == 500000000);
// /* 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();
}

View File

@ -0,0 +1,96 @@
/* Eduardo: testing payflow_map.
* */
#include "config.h"
#include <ccan/array_size/array_size.h>
#include <ccan/read_write_all/read_write_all.h>
#include <common/bigsize.h>
#include <common/channel_id.h>
#include <common/gossip_store.h>
#include <common/node_id.h>
#include <common/setup.h>
#include <common/type_to_string.h>
#include <common/wireaddr.h>
#include <stdio.h>
#include <assert.h>
#include <bitcoin/short_channel_id.h>
#include <ccan/htable/htable_type.h>
#define RENEPAY_UNITTEST
#include <plugins/renepay/pay_flow.h>
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();
}

View File

@ -0,0 +1,696 @@
#include "config.h"
#include <stdio.h>
#include <assert.h>
#include <common/wireaddr.h>
#include <common/bigsize.h>
#include <common/channel_id.h>
#include <common/setup.h>
#include <common/utils.h>
#include <common/node_id.h>
#include <ccan/read_write_all/read_write_all.h>
#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<A, f<A-X
for(int i=0;i<=5;++i)
{
f.millisatoshis = i;
// prob = 1
assert(fabs(edge_probability(min,max,X,f)-1.0)< eps);
}
// X<A, f>=B-X
for(int i=15;i<100;++i)
{
f.millisatoshis = i;
// prob = 0
assert(fabs(edge_probability(min,max,X,f))< eps);
}
// X<A, A-X<=f<=B-X
f.millisatoshis=6;
assert(fabs(edge_probability(min,max,X,f)-0.9)< eps);
f.millisatoshis=7;
assert(fabs(edge_probability(min,max,X,f)-0.8)< eps);
f.millisatoshis=8;
assert(fabs(edge_probability(min,max,X,f)-0.7)< eps);
f.millisatoshis=9;
assert(fabs(edge_probability(min,max,X,f)-0.6)< eps);
f.millisatoshis=10;
assert(fabs(edge_probability(min,max,X,f)-0.5)< eps);
f.millisatoshis=11;
assert(fabs(edge_probability(min,max,X,f)-0.4)< eps);
f.millisatoshis=12;
assert(fabs(edge_probability(min,max,X,f)-0.3)< eps);
f.millisatoshis=13;
assert(fabs(edge_probability(min,max,X,f)-0.2)< eps);
f.millisatoshis=14;
assert(fabs(edge_probability(min,max,X,f)-0.1)< eps);
X = AMOUNT_MSAT(15);
// f>=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;i<node->num_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)<eps);
// flow 3->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.)<eps);
// flow 2->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)<eps);
// flow 1->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)<eps);
tal_free(this_ctx);
}
int main(int argc, char *argv[])
{
common_setup(argv[0]);
test_edge_probability();
test_flow_complete();
common_shutdown();
}

View File

@ -0,0 +1,348 @@
#include "config.h"
#include <plugins/renepay/debug.h>
#include <plugins/renepay/uncertainty_network.h>
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;i<tal_count(del_list);++i)
{
struct chan_extra *ce = chan_extra_map_get(chan_extra_map,del_list[i]);
if(!ce)
{
debug_err("%s (line %d) unexpected chan_extra ce is NULL",
__PRETTY_FUNCTION__,
__LINE__);
}
chan_extra_map_del(chan_extra_map, ce);
tal_free(ce);
// TODO(eduardo): if you had added a destructor to ce, you could have removed
// the ce from the map automatically.
}
// For each channel in the gossmap, create a extra data in
// chan_extra_map
for(struct gossmap_chan *chan = gossmap_first_chan(gossmap);
chan;
chan=gossmap_next_chan(gossmap,chan))
{
struct short_channel_id scid =
gossmap_chan_scid(gossmap,chan);
struct chan_extra *ce = chan_extra_map_get(chan_extra_map,
gossmap_chan_scid(gossmap,chan));
if(!ce)
{
struct amount_sat cap;
struct amount_msat cap_msat;
if(!gossmap_chan_get_capacity(gossmap,chan,&cap))
{
debug_err("%s (line %d) unable to fetch channel capacity",
__PRETTY_FUNCTION__,
__LINE__);
}
if(!amount_sat_to_msat(&cap_msat,cap))
{
debug_err("%s (line %d) unable convert sat to msat",
__PRETTY_FUNCTION__,
__LINE__);
}
new_chan_extra(chan_extra_map,scid,cap_msat);
}
}
tal_free(this_ctx);
}
void uncertainty_network_flow_success(
struct chan_extra_map *chan_extra_map,
struct pay_flow *flow)
{
for (size_t i = 0; i < tal_count(flow->path_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;
}

View File

@ -0,0 +1,47 @@
#ifndef LIGHTNING_PLUGINS_RENEPAY_UNCERTAINTY_NETWORK_H
#define LIGHTNING_PLUGINS_RENEPAY_UNCERTAINTY_NETWORK_H
#include "config.h"
#include <common/gossmap.h>
#include <plugins/renepay/flow.h>
#include <plugins/renepay/pay_flow.h>
#include <plugins/renepay/payment.h>
struct pay_flow;
/* 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 */

239
tests/test_renepay.py Normal file
View File

@ -0,0 +1,239 @@
from fixtures import * # noqa: F401,F403
from pyln.client import RpcError, Millisatoshi
from utils import only_one, wait_for, mine_funding_to_announce
import pytest
import random
import time
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']
details = l1.rpc.call('renepay', {'invstring': inv})
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, bitcoind):
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):
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):
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)
l4.rpc.connect(l6.info['id'], 'localhost', l6.port)
l5.rpc.connect(l6.info['id'], 'localhost', l6.port)
scid46, _ = l4.fundchannel(l6, 10**6, wait_for_active=False)
scid56, _ = l5.fundchannel(l6, 10**6, wait_for_active=False)
mine_funding_to_announce(bitcoind, [l1, l2, l3, l4, l5, l6])
l1.daemon.wait_for_logs([r'update for channel {}/0 now ACTIVE'
.format(scid46),
r'update for channel {}/1 now ACTIVE'
.format(scid46),
r'update for channel {}/0 now ACTIVE'
.format(scid56),
r'update for channel {}/1 now ACTIVE'
.format(scid56)])
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']
@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
# Make sure they're completely settled, so accounting correct.
wait_for(lambda: only_one(l1.rpc.listpeerchannels()['channels'])['htlcs'] == [])
# 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):
'''
Check that the amount received matches the amount requested in the invoice.
'''
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
Try the error messages when paying when:
- the fees are too high,
- CLTV delay is too high,
- probability of success is too low.
'''
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')