mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-02-22 22:45:27 +01:00
renepay: fixups after comments
- remove internal gheap checks - add check for arc_t.chanidx overflow - remove outdated comments - check the delta flow bounds before augmenting along a path - get_flow_paths uses a dynamic tal array instead of a list. - fix a unit test that depended on the order of returned flows - fix bug: lightnind doesn't like if I reuse the partid of a failed flow, therefore use a higher partid than any of the previous attempts. - plugin_err instead of LOG_BROKEN if sendpay fails and we cannot get a an error code. - fix wrong comments. - remove the background timer. - This is a bugfix. Previous to this the MCF network was built using the knowledge of the min and max liquidity but it didn't take into account pending HTLCs. - Also remove the min_prob_success option but hardcode a 90% value. Removing some options that are not relevant to the user, they're kept for developer mode only: - base_fee_penalty - min_prob_success - prob_cost_factor - remove heap.h, not used Signed-off-by: Lagrang3 <eduardo.quintana@pm.me>
This commit is contained in:
parent
d46990d301
commit
79486c1e3b
9 changed files with 174 additions and 350 deletions
|
@ -1,6 +1,6 @@
|
|||
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 \
|
||||
PLUGIN_RENEPAY_HDRS := plugins/renepay/pay.h plugins/renepay/pay_flow.h plugins/renepay/flow.h plugins/renepay/mcf.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)
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#define NDEBUG 1
|
||||
#include "config.h"
|
||||
#include <plugins/renepay/dijkstra.h>
|
||||
|
||||
|
|
|
@ -1,195 +0,0 @@
|
|||
#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 */
|
|
@ -409,11 +409,12 @@ static arc_t channel_idx_to_arc(
|
|||
int dual)
|
||||
{
|
||||
arc_t arc;
|
||||
// arc.idx=0; // shouldn't be necessary, but valgrind complains of uninitialized field idx
|
||||
arc.dual=dual;
|
||||
arc.part=part;
|
||||
arc.chandir=half;
|
||||
arc.chanidx = chan_idx;
|
||||
/* check that it doesn't overflow */
|
||||
assert(arc.chanidx == chan_idx);
|
||||
return arc;
|
||||
}
|
||||
|
||||
|
@ -439,9 +440,26 @@ static void linearize_channel(
|
|||
__LINE__);
|
||||
}
|
||||
|
||||
s64 h = extra_half->htlc_total.millisatoshis/1000; /* Raw: linearize_channel */
|
||||
s64 a = extra_half->known_min.millisatoshis/1000, /* Raw: linearize_channel */
|
||||
b = 1 + extra_half->known_max.millisatoshis/1000; /* Raw: linearize_channel */
|
||||
|
||||
/* If HTLCs add up to more than the known_max it means we have a
|
||||
* completely wrong knowledge. */
|
||||
// assert(h<b);
|
||||
/* HTLCs allocated could instead be greater than known_min, we enter in
|
||||
* the uncertainty region. If h>a it doesn't mean automatically that our
|
||||
* known_min should have been updated, because we reserve this HTLC
|
||||
* after sendpay behind the scenes it might happen that sendpay failed
|
||||
* because of insufficient funds we haven't noticed yet. */
|
||||
// assert(h<=a);
|
||||
|
||||
/* We reduce this channel capacity because HTLC are reserving liquidity. */
|
||||
a -= h;
|
||||
b -= h;
|
||||
a = MAX(a,0);
|
||||
b = MAX(a+1,b);
|
||||
|
||||
capacity[0]=a;
|
||||
cost[0]=0;
|
||||
for(size_t i=1;i<CHANNEL_PARTS;++i)
|
||||
|
@ -579,7 +597,6 @@ static void init_linear_network(
|
|||
const struct gossmap_chan *c = gossmap_nth_chan(params->gossmap,
|
||||
node, j, &half);
|
||||
|
||||
// TODO(eduardo): in which case can this be triggered?
|
||||
if (!gossmap_chan_set(c,half))
|
||||
continue;
|
||||
|
||||
|
@ -818,9 +835,9 @@ static int find_feasible_flow(
|
|||
|
||||
// commit that flow to the path
|
||||
delta = MIN(amount,delta);
|
||||
augment_flow(linear_network,residual_network,source,target,prev,delta);
|
||||
|
||||
assert(delta>0 && delta<=amount);
|
||||
|
||||
augment_flow(linear_network,residual_network,source,target,prev,delta);
|
||||
amount -= delta;
|
||||
}
|
||||
|
||||
|
@ -961,9 +978,9 @@ static int optimize_mcf(
|
|||
|
||||
// commit that flow to the path
|
||||
delta = MIN(remaining_amount,delta);
|
||||
augment_flow(linear_network,residual_network,source,target,prev,delta);
|
||||
|
||||
assert(delta>0 && delta<=remaining_amount);
|
||||
|
||||
augment_flow(linear_network,residual_network,source,target,prev,delta);
|
||||
remaining_amount -= delta;
|
||||
|
||||
// update potentials
|
||||
|
@ -1066,7 +1083,6 @@ struct list_data
|
|||
struct flow *flow_path;
|
||||
};
|
||||
|
||||
// TODO(eduardo): check this
|
||||
/* Given a flow in the residual network, build a set of payment flows in the
|
||||
* gossmap that corresponds to this flow. */
|
||||
static struct flow **
|
||||
|
@ -1123,10 +1139,7 @@ static struct flow **
|
|||
}
|
||||
|
||||
|
||||
size_t num_paths=0;
|
||||
tal_t *list_ctx = tal(this_ctx,tal_t);
|
||||
LIST_HEAD(path_list);
|
||||
struct list_data *ld;
|
||||
struct flow **flows = tal_arr(ctx,struct flow*,0);
|
||||
|
||||
// Select all nodes with negative balance and find a flow that reaches a
|
||||
// positive balance node.
|
||||
|
@ -1170,7 +1183,7 @@ static struct flow **
|
|||
}
|
||||
|
||||
|
||||
struct flow *fp = tal(list_ctx,struct flow);
|
||||
struct flow *fp = tal(this_ctx,struct flow);
|
||||
fp->path = tal_arr(fp,struct gossmap_chan const*,length);
|
||||
fp->dirs = tal_arr(fp,int,length);
|
||||
|
||||
|
@ -1214,22 +1227,16 @@ static struct flow **
|
|||
// probabilities.
|
||||
flow_complete(fp,gossmap,chan_extra_map,delivered);
|
||||
|
||||
// add fp to list
|
||||
ld = tal(list_ctx,struct list_data);
|
||||
ld->flow_path = fp;
|
||||
list_add(&path_list,&ld->list);
|
||||
num_paths++;
|
||||
// add fp to flows
|
||||
tal_arr_expand(&flows, fp);
|
||||
}
|
||||
}
|
||||
|
||||
// copy the list into the array we are going to return
|
||||
struct flow **flows = tal_arr(ctx,struct flow*,num_paths);
|
||||
size_t pos=0;
|
||||
list_for_each(&path_list,ld,list)
|
||||
/* Stablish ownership. */
|
||||
for(int i=0;i<tal_count(flows);++i)
|
||||
{
|
||||
flows[pos++] = tal_steal(flows,ld->flow_path);
|
||||
flows[i] = tal_steal(flows,flows[i]);
|
||||
}
|
||||
|
||||
tal_free(this_ctx);
|
||||
return flows;
|
||||
}
|
||||
|
|
|
@ -20,8 +20,14 @@
|
|||
// TODO(eduardo): maybe there are too many debug_err and plugin_err and
|
||||
// plugin_log(...,LOG_BROKEN,...) that could be resolved with a command_fail
|
||||
|
||||
// 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.
|
||||
|
||||
#define INVALID_ID UINT64_MAX
|
||||
#define MAX(a,b) ((a)>(b)? (a) : (b))
|
||||
#define MIN(a,b) ((a)<(b)? (a) : (b))
|
||||
|
||||
static struct pay_plugin the_pay_plugin;
|
||||
struct pay_plugin * const pay_plugin = &the_pay_plugin;
|
||||
|
@ -31,10 +37,6 @@ static struct command_result *try_paying(struct command *cmd,
|
|||
struct renepay * renepay,
|
||||
bool first_time);
|
||||
|
||||
// TODO(eduardo): maybe we don't need these
|
||||
static void background_timer_kick(void*p UNUSED);
|
||||
static void background_settimer(void);
|
||||
|
||||
void amount_msat_accumulate_(struct amount_msat *dst,
|
||||
struct amount_msat src,
|
||||
const char *dstname,
|
||||
|
@ -92,7 +94,6 @@ static const char *init(struct plugin *p,
|
|||
|
||||
pay_plugin->ctx = notleak_with_children(tal(p,tal_t));
|
||||
pay_plugin->plugin = p;
|
||||
pay_plugin->rexmit_timer=NULL;
|
||||
pay_plugin->last_time = 0;
|
||||
|
||||
rpc_scan(p, "getinfo", take(json_out_obj(NULL, NULL, NULL)),
|
||||
|
@ -132,57 +133,10 @@ static const char *init(struct plugin *p,
|
|||
#if DEVELOPER
|
||||
plugin_set_memleak_handler(p, memleak_mark);
|
||||
#endif
|
||||
|
||||
background_settimer();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
// /* TODO(eduardo): an example of an RPC call that is not bound to any command. */
|
||||
//static
|
||||
//struct command_result* getinfo_done(struct command *cmd UNUSED,
|
||||
// const char *buf,
|
||||
// const jsmntok_t *result,
|
||||
// void* pp UNUSED)
|
||||
//{
|
||||
// struct node_id id;
|
||||
// const jsmntok_t *id_tok = json_get_member(buf,result,"id");
|
||||
// json_to_node_id(buf,id_tok,&id);
|
||||
//
|
||||
// plugin_log(pay_plugin->plugin,LOG_DBG,
|
||||
// "calling %s, nodeid = %s",
|
||||
// __PRETTY_FUNCTION__,
|
||||
// type_to_string(tmpctx,struct node_id,&id));
|
||||
//
|
||||
// return command_still_pending(NULL);
|
||||
//}
|
||||
|
||||
static void background_settimer(void)
|
||||
{
|
||||
pay_plugin->rexmit_timer
|
||||
= tal_free(pay_plugin->rexmit_timer);
|
||||
pay_plugin->rexmit_timer
|
||||
= plugin_timer(
|
||||
pay_plugin->plugin,
|
||||
time_from_msec(2000),
|
||||
background_timer_kick, NULL);
|
||||
}
|
||||
|
||||
static void background_timer_kick(void * p UNUSED)
|
||||
{
|
||||
// plugin_log(pay_plugin->plugin,LOG_DBG,"calling %s",__PRETTY_FUNCTION__);
|
||||
background_settimer();
|
||||
|
||||
// /* TODO(eduardo): an example of an RPC call that is not bound to any command. */
|
||||
// struct out_req * req = jsonrpc_request_start(pay_plugin->plugin,
|
||||
// NULL,
|
||||
// "getinfo",
|
||||
// getinfo_done,
|
||||
// getinfo_done,
|
||||
// NULL);
|
||||
// send_outreq(pay_plugin->plugin, req);
|
||||
}
|
||||
|
||||
static void renepay_settimer(struct renepay * renepay)
|
||||
{
|
||||
renepay->rexmit_timer = tal_free(renepay->rexmit_timer);
|
||||
|
@ -202,17 +156,20 @@ static void timer_kick(struct renepay * renepay)
|
|||
{
|
||||
/* Some flows succeeded, we finish the payment. */
|
||||
case PAYMENT_SUCCESS:
|
||||
plugin_log(pay_plugin->plugin,LOG_DBG,"status is PAYMENT_SUCCESS");
|
||||
renepay_success(renepay);
|
||||
break;
|
||||
|
||||
/* Some flows failed, we retry. */
|
||||
case PAYMENT_FAIL:
|
||||
plugin_log(pay_plugin->plugin,LOG_DBG,"status is PAYMENT_FAIL");
|
||||
payment_assert_delivering_incomplete(p);
|
||||
try_paying(renepay->cmd,renepay,false);
|
||||
try_paying(renepay->cmd,renepay,/* always try even if prob is low */ true);
|
||||
break;
|
||||
|
||||
/* Nothing has returned yet, we have to wait. */
|
||||
case PAYMENT_PENDING:
|
||||
plugin_log(pay_plugin->plugin,LOG_DBG,"status is PAYMENT_PENDING");
|
||||
payment_assert_delivering_all(p);
|
||||
renepay_settimer(renepay);
|
||||
break;
|
||||
|
@ -454,6 +411,12 @@ sendpay_flows(struct command *cmd,
|
|||
debug_paynote(p, "Sending out batch of %zu payments", tal_count(flows));
|
||||
|
||||
for (size_t i = 0; i < tal_count(flows); i++) {
|
||||
const u64 path_lengh = tal_count(flows[i]->amounts);
|
||||
debug_paynote(p, "sendpay flow groupid=%ld, partid=%ld, delivering=%s",
|
||||
flows[i]->key.groupid,
|
||||
flows[i]->key.partid,
|
||||
type_to_string(tmpctx,struct amount_msat,
|
||||
&flows[i]->amounts[path_lengh-1]));
|
||||
struct out_req *req;
|
||||
req = jsonrpc_request_start(cmd->plugin, cmd, "sendpay",
|
||||
flow_sent, flow_sendpay_failed,
|
||||
|
@ -607,7 +570,7 @@ static struct command_result *try_paying(struct command *cmd,
|
|||
|
||||
/* would you accept unlikely
|
||||
* payments? */
|
||||
first_time,
|
||||
true,
|
||||
|
||||
/* is entire payment? */
|
||||
amount_msat_eq(p->total_delivering, AMOUNT_MSAT(0)),
|
||||
|
@ -617,7 +580,6 @@ static struct command_result *try_paying(struct command *cmd,
|
|||
// plugin_log(pay_plugin->plugin,LOG_DBG,"get_payflows produced %s",fmt_payflows(tmpctx,pay_flows));
|
||||
|
||||
/* MCF cannot find a feasible route, we stop. */
|
||||
// TODO(eduardo): alternatively we can fallback to `pay`.
|
||||
if (!pay_flows)
|
||||
{
|
||||
return renepay_fail(renepay, PAY_ROUTE_NOT_FOUND,
|
||||
|
@ -787,6 +749,10 @@ payment_listsendpays_previous(
|
|||
cmd, LIGHTNINGD,
|
||||
"Unexpected non-array result from listsendpays");
|
||||
|
||||
/* We need two scans of the payments, the first to identify the groupid
|
||||
* that have pending sendpays and the second to get the maximum partid
|
||||
* from that group. */
|
||||
|
||||
/* We iterate through all prior sendpays, looking for the
|
||||
* latest group and remembering what its state is. */
|
||||
json_for_each_arr(i, t, arr)
|
||||
|
@ -807,9 +773,12 @@ payment_listsendpays_previous(
|
|||
JSON_SCAN(json_to_msat,&this_msat),
|
||||
JSON_SCAN(json_to_msat,&this_sent));
|
||||
|
||||
if(last_group==INVALID_ID)
|
||||
last_group = groupid;
|
||||
|
||||
last_group = MAX(last_group,groupid);
|
||||
|
||||
/* status could be completed, pending or failed */
|
||||
|
||||
|
||||
status = json_get_member(buf, t, "status");
|
||||
|
||||
if(json_tok_streq(buf,status,"failed"))
|
||||
|
@ -841,32 +810,54 @@ payment_listsendpays_previous(
|
|||
last_pending_group_id==INVALID_ID)
|
||||
first_pending_group_id = last_pending_group_id = groupid;
|
||||
|
||||
if(groupid > last_pending_group_id)
|
||||
{
|
||||
last_pending_group_id = groupid;
|
||||
last_pending_partid = partid;
|
||||
pending_msat = AMOUNT_MSAT(0);
|
||||
pending_sent = AMOUNT_MSAT(0);
|
||||
}
|
||||
if(groupid < first_pending_group_id)
|
||||
{
|
||||
first_pending_group_id = groupid;
|
||||
}
|
||||
if(groupid == last_pending_group_id)
|
||||
{
|
||||
amount_msat_accumulate(&pending_sent,this_sent);
|
||||
amount_msat_accumulate(&pending_msat,this_msat);
|
||||
|
||||
last_pending_partid = MAX(last_pending_partid,partid);
|
||||
plugin_log(pay_plugin->plugin,LOG_DBG,
|
||||
"pending deliver increased by %s",
|
||||
type_to_string(tmpctx,struct amount_msat,&this_msat));
|
||||
}
|
||||
|
||||
last_pending_group_id = MAX(last_pending_group_id,groupid);
|
||||
first_pending_group_id = MIN(first_pending_group_id,groupid);
|
||||
}
|
||||
}
|
||||
|
||||
/* We iterate through all prior sendpays, looking for the
|
||||
* latest pending group. */
|
||||
json_for_each_arr(i, t, arr)
|
||||
{
|
||||
u64 partid, groupid;
|
||||
struct amount_msat this_msat, this_sent;
|
||||
|
||||
const jsmntok_t *status;
|
||||
|
||||
// TODO(eduardo): assuming amount_msat is always known.
|
||||
json_scan(tmpctx,buf,t,
|
||||
"{partid:%"
|
||||
",groupid:%"
|
||||
",amount_msat:%"
|
||||
",amount_sent_msat:%}",
|
||||
JSON_SCAN(json_to_u64,&partid),
|
||||
JSON_SCAN(json_to_u64,&groupid),
|
||||
JSON_SCAN(json_to_msat,&this_msat),
|
||||
JSON_SCAN(json_to_msat,&this_sent));
|
||||
|
||||
/* status could be completed, pending or failed */
|
||||
status = json_get_member(buf, t, "status");
|
||||
|
||||
/* It seems I cannot reuse failed partids for the same groupid,
|
||||
* therefore let's count them all whatever the status. */
|
||||
if(groupid==last_pending_group_id)
|
||||
last_pending_partid = MAX(last_pending_partid,partid);
|
||||
|
||||
if(groupid == last_pending_group_id && json_tok_streq(buf,status,"pending"))
|
||||
{
|
||||
amount_msat_accumulate(&pending_sent,this_sent);
|
||||
amount_msat_accumulate(&pending_msat,this_msat);
|
||||
|
||||
plugin_log(pay_plugin->plugin,LOG_DBG,
|
||||
"pending deliver increased by %s",
|
||||
type_to_string(tmpctx,struct amount_msat,&this_msat));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (completed) {
|
||||
/* There are completed sendpays, we don't need to do anything
|
||||
* but summarize the result. */
|
||||
struct json_stream *ret = jsonrpc_stream_success(cmd);
|
||||
json_add_preimage(ret, "payment_preimage", &complete_preimage);
|
||||
json_add_string(ret, "status", "complete");
|
||||
|
@ -883,6 +874,9 @@ payment_listsendpays_previous(
|
|||
|
||||
return command_finished(cmd, ret);
|
||||
} else if (pending) {
|
||||
assert(last_pending_group_id!=INVALID_ID);
|
||||
assert(first_pending_group_id!=INVALID_ID);
|
||||
|
||||
p->groupid = last_pending_group_id;
|
||||
renepay->next_partid = last_pending_partid+1;
|
||||
|
||||
|
@ -916,6 +910,8 @@ payment_listsendpays_previous(
|
|||
}
|
||||
}else
|
||||
{
|
||||
/* There are no pending nor completed sendpays, get me the last
|
||||
* sendpay group. */
|
||||
p->groupid = (last_group==INVALID_ID ? 1 : (last_group+1)) ;
|
||||
renepay->next_partid=1;
|
||||
}
|
||||
|
@ -940,14 +936,14 @@ static struct command_result *json_pay(struct command *cmd,
|
|||
u64 invexpiry;
|
||||
struct amount_msat *msat, *invmsat;
|
||||
struct amount_msat *maxfee;
|
||||
u64 *riskfactor_millionths;
|
||||
u32 *maxdelay;
|
||||
u64 *base_fee_penalty;
|
||||
u64 *prob_cost_factor;
|
||||
u64 *min_prob_success_millionths;
|
||||
u32 *retryfor;
|
||||
|
||||
#if DEVELOPER
|
||||
u64 *base_fee_penalty;
|
||||
u64 *prob_cost_factor;
|
||||
u64 *riskfactor_millionths;
|
||||
u64 *min_prob_success_millionths;
|
||||
bool *use_shadow;
|
||||
#endif
|
||||
|
||||
|
@ -956,15 +952,6 @@ static struct command_result *json_pay(struct command *cmd,
|
|||
p_opt("amount_msat", param_msat, &msat),
|
||||
p_opt("maxfee", param_msat, &maxfee),
|
||||
|
||||
// MCF parameters
|
||||
// TODO(eduardo): are these parameters read correctly?
|
||||
p_opt_def("base_fee_penalty", param_millionths, &base_fee_penalty,10),
|
||||
p_opt_def("prob_cost_factor", param_millionths, &prob_cost_factor,10),
|
||||
p_opt_def("min_prob_success", param_millionths,
|
||||
&min_prob_success_millionths,100000),// default is 10%
|
||||
|
||||
p_opt_def("riskfactor", param_millionths,&riskfactor_millionths,1),
|
||||
|
||||
p_opt_def("maxdelay", param_number, &maxdelay,
|
||||
/* We're initially called to probe usage, before init! */
|
||||
pay_plugin ? pay_plugin->maxdelay_default : 0),
|
||||
|
@ -975,6 +962,13 @@ static struct command_result *json_pay(struct command *cmd,
|
|||
p_opt("description", param_string, &description),
|
||||
p_opt("label", param_string, &label),
|
||||
#if DEVELOPER
|
||||
// MCF parameters
|
||||
// TODO(eduardo): are these parameters read correctly?
|
||||
p_opt_def("base_fee_penalty", param_millionths, &base_fee_penalty,10),
|
||||
p_opt_def("prob_cost_factor", param_millionths, &prob_cost_factor,10),
|
||||
p_opt_def("riskfactor", param_millionths,&riskfactor_millionths,1),
|
||||
p_opt_def("min_prob_success", param_millionths,
|
||||
&min_prob_success_millionths,900000),// default is 90%
|
||||
p_opt_def("use_shadow", param_bool, &use_shadow, true),
|
||||
#endif
|
||||
NULL))
|
||||
|
@ -993,6 +987,23 @@ static struct command_result *json_pay(struct command *cmd,
|
|||
p->label = tal_steal(p,label);
|
||||
p->local_offer_id = tal_steal(p,local_offer_id);
|
||||
|
||||
|
||||
|
||||
/* Please renepay try to give me a reliable payment 90% chances of
|
||||
* success, once you do, then minimize as much as possible those fees. */
|
||||
p->min_prob_success = 0.9;
|
||||
|
||||
/* Default delay_feefactor: how much shall we penalize for delay. */
|
||||
p->delay_feefactor = 1e-6;
|
||||
|
||||
/* Default prob_cost_factor: how to convert prob. cost to sats. */
|
||||
p->prob_cost_factor = 10;
|
||||
|
||||
/* Default base_fee_penalty: how to convert a base fee into a
|
||||
* proportional fee. */
|
||||
p->base_fee_penalty = 10;
|
||||
|
||||
#if DEVELOPER
|
||||
p->base_fee_penalty = *base_fee_penalty;
|
||||
base_fee_penalty = tal_free(base_fee_penalty);
|
||||
|
||||
|
@ -1004,6 +1015,7 @@ static struct command_result *json_pay(struct command *cmd,
|
|||
|
||||
p->delay_feefactor = *riskfactor_millionths/1e6;
|
||||
riskfactor_millionths = tal_free(riskfactor_millionths);
|
||||
#endif
|
||||
|
||||
p->maxdelay = *maxdelay;
|
||||
maxdelay = tal_free(maxdelay);
|
||||
|
@ -1456,7 +1468,7 @@ static void handle_sendpay_failure_flow(
|
|||
u64 errcode;
|
||||
if (!json_to_u64(buf, json_get_member(buf, result, "code"), &errcode))
|
||||
{
|
||||
plugin_log(pay_plugin->plugin,LOG_BROKEN,
|
||||
plugin_err(pay_plugin->plugin,
|
||||
"Failed to get code from sendpay_failure notification"
|
||||
", received json: %.*s",
|
||||
json_tok_full_len(result),
|
||||
|
|
|
@ -78,22 +78,17 @@ struct pay_plugin {
|
|||
/* 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;
|
||||
/* Pending flows have HTLCs (in-flight) liquidity
|
||||
* attached that is reflected in the uncertainty network.
|
||||
* When sendpay_fail or sendpay_success notifications arrive
|
||||
* 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 flows to pay_plugin->ctx so that the
|
||||
* flows are kept alive.
|
||||
*
|
||||
* TODO(eduardo): maybe we should add a check to ensure that pending
|
||||
* flows are not accumulating ad-infinitum. */
|
||||
|
||||
/* It allows us to measure elapsed time
|
||||
* and forget channel information accordingly. */
|
||||
|
|
|
@ -82,7 +82,7 @@ struct payment {
|
|||
|
||||
/* Conversion from prob. cost to millionths */
|
||||
double prob_cost_factor;
|
||||
/* linear prob. cost =
|
||||
/* prob. cost =
|
||||
* - prob_cost_factor * log prob. */
|
||||
|
||||
|
||||
|
|
|
@ -17,6 +17,13 @@
|
|||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
|
||||
static void swap(int *a, int *b)
|
||||
{
|
||||
int temp = *a;
|
||||
*a = *b;
|
||||
*b = temp;
|
||||
}
|
||||
|
||||
/* 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
|
||||
*/
|
||||
|
@ -451,20 +458,29 @@ int main(int argc, char *argv[])
|
|||
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);
|
||||
|
||||
/* The solution is composed by two paths, one with lenght 1 and the
|
||||
* other with lenght 2. There is no guaranteed order of the solutions
|
||||
* returning from minflow, hence we need to test them. */
|
||||
int ID1 = 0, ID2 = 1;
|
||||
if(tal_count(flows2[ID1]->path)==2)
|
||||
{
|
||||
swap(&ID1,&ID2);
|
||||
}
|
||||
assert(tal_count(flows2[ID1]->path) == 1);
|
||||
assert(tal_count(flows2[ID2]->path) == 2);
|
||||
|
||||
// /* Sends more via 1->3, since it's more expensive (but lower prob) */
|
||||
assert(amount_msat_greater(flows2[0]->amounts[0], flows2[1]->amounts[0]));
|
||||
assert(flows2[0]->success_prob < flows2[1]->success_prob);
|
||||
assert(amount_msat_greater(flows2[ID1]->amounts[0], flows2[ID2]->amounts[0]));
|
||||
assert(flows2[ID1]->success_prob < flows2[ID2]->success_prob);
|
||||
|
||||
/* Delivered amount must be the total! */
|
||||
assert(flows2[0]->amounts[0].millisatoshis
|
||||
+ flows2[1]->amounts[1].millisatoshis == 500000000);
|
||||
assert(flows2[ID1]->amounts[0].millisatoshis
|
||||
+ flows2[ID2]->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);
|
||||
assert(flows2[ID1]->amounts[0].millisatoshis + flows2[ID2]->amounts[0].millisatoshis
|
||||
> flows2[ID1]->amounts[0].millisatoshis - flows2[ID2]->amounts[0].millisatoshis);
|
||||
|
||||
common_shutdown();
|
||||
}
|
||||
|
|
|
@ -60,11 +60,6 @@ def test_errors(node_factory, bitcoind):
|
|||
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)
|
||||
|
||||
|
@ -225,15 +220,8 @@ def test_limits(node_factory):
|
|||
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})
|
||||
'renepay', {'invstring': inv2['bolt11']})
|
||||
invoice = only_one(l6.rpc.listinvoices('inv2')['invoices'])
|
||||
assert isinstance(invoice['amount_received_msat'], Millisatoshi)
|
||||
assert invoice['amount_received_msat'] >= Millisatoshi('800000sat')
|
||||
|
|
Loading…
Add table
Reference in a new issue