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:
Lagrang3 2023-07-31 11:21:25 +09:30 committed by Rusty Russell
parent d46990d301
commit 79486c1e3b
9 changed files with 174 additions and 350 deletions

View file

@ -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)

View file

@ -1,3 +1,4 @@
#define NDEBUG 1
#include "config.h"
#include <plugins/renepay/dijkstra.h>

View file

@ -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 */

View file

@ -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;
}

View file

@ -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),

View file

@ -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. */

View file

@ -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. */

View file

@ -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();
}

View file

@ -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')