mirror of
https://github.com/ElementsProject/lightning.git
synced 2024-11-19 18:11:28 +01:00
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:
parent
c02f175a75
commit
b8ca193606
@ -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
16
plugins/renepay/Makefile
Normal 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
51
plugins/renepay/debug.c
Normal 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
54
plugins/renepay/debug.h
Normal 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
173
plugins/renepay/dijkstra.c
Normal 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;
|
||||
}
|
49
plugins/renepay/dijkstra.h
Normal file
49
plugins/renepay/dijkstra.h
Normal 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
828
plugins/renepay/flow.c
Normal 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
319
plugins/renepay/flow.h
Normal 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
195
plugins/renepay/heap.h
Normal 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
1507
plugins/renepay/mcf.c
Normal file
File diff suppressed because it is too large
Load Diff
71
plugins/renepay/mcf.h
Normal file
71
plugins/renepay/mcf.h
Normal 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
1792
plugins/renepay/pay.c
Normal file
File diff suppressed because it is too large
Load Diff
117
plugins/renepay/pay.h
Normal file
117
plugins/renepay/pay.h
Normal 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
633
plugins/renepay/pay_flow.c
Normal 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
112
plugins/renepay/pay_flow.h
Normal 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
230
plugins/renepay/payment.c
Normal 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
163
plugins/renepay/payment.h
Normal 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 */
|
19
plugins/renepay/test/Makefile
Normal file
19
plugins/renepay/test/Makefile
Normal 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
|
100
plugins/renepay/test/run-dijkstra.c
Normal file
100
plugins/renepay/test/run-dijkstra.c
Normal 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();
|
||||
}
|
156
plugins/renepay/test/run-mcf-diamond.c
Normal file
156
plugins/renepay/test/run-mcf-diamond.c
Normal 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();
|
||||
}
|
470
plugins/renepay/test/run-mcf.c
Normal file
470
plugins/renepay/test/run-mcf.c
Normal 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();
|
||||
}
|
96
plugins/renepay/test/run-payflow_map.c
Normal file
96
plugins/renepay/test/run-payflow_map.c
Normal 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();
|
||||
}
|
||||
|
696
plugins/renepay/test/run-testflow.c
Normal file
696
plugins/renepay/test/run-testflow.c
Normal 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();
|
||||
}
|
||||
|
348
plugins/renepay/uncertainty_network.c
Normal file
348
plugins/renepay/uncertainty_network.c
Normal 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;
|
||||
}
|
47
plugins/renepay/uncertainty_network.h
Normal file
47
plugins/renepay/uncertainty_network.h
Normal 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
239
tests/test_renepay.py
Normal 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')
|
Loading…
Reference in New Issue
Block a user