Skip to content

Commit f02e1b7

Browse files
committed
askrene: add internal API for single-path routes
The single path solver uses the same probability cost and fee cost estimation of minflow. Single path routes computed this way are suboptimal with respect to the MCF solution but still are optimal among any other single path. Computationally is way faster than MCF, therefore for some trivial payments it should be prefered. Changelog-None. Signed-off-by: Lagrang3 <[email protected]>
1 parent 0922d55 commit f02e1b7

File tree

2 files changed

+282
-3
lines changed

2 files changed

+282
-3
lines changed

plugins/askrene/mcf.c

Lines changed: 260 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include <assert.h>
33
#include <ccan/asort/asort.h>
44
#include <ccan/bitmap/bitmap.h>
5+
#include <ccan/err/err.h>
56
#include <ccan/list/list.h>
67
#include <ccan/tal/str/str.h>
78
#include <ccan/tal/tal.h>
@@ -159,6 +160,10 @@
159160
*
160161
* */
161162

163+
#define PANIC(message) \
164+
errx(1, "Panic in function %s line %d: %s", __func__, __LINE__, \
165+
message);
166+
162167
#define PARTS_BITS 2
163168
#define CHANNEL_PARTS (1 << PARTS_BITS)
164169

@@ -297,6 +302,10 @@ struct pay_parameters {
297302

298303
double delay_feefactor;
299304
double base_fee_penalty;
305+
306+
/* keep this here to have a consistent conversion factor all over.
307+
* Probability Cost (x) = prob_cost_factor * (-log Probability(x)) */
308+
double prob_cost_factor;
300309
};
301310

302311
/* Helper function.
@@ -319,6 +328,46 @@ static void set_capacity(s64 *capacity, u64 value, u64 *cap_on_capacity)
319328
*cap_on_capacity -= *capacity;
320329
}
321330

331+
/* FIXME: unit test this */
332+
/* The probability of forwarding a payment amount given a high and low liquidity
333+
* bounds.
334+
* @low: the liquidity is known to be greater or equal than "low"
335+
* @high: the liquidity is known to be less than "high"
336+
* @amount: how much is required to forward */
337+
static double pickhardt_richter_probability(struct amount_msat low,
338+
struct amount_msat high,
339+
struct amount_msat amount)
340+
{
341+
struct amount_msat all_states, good_states;
342+
if (amount_msat_greater_eq(amount, high))
343+
return 0.0;
344+
if (!amount_msat_sub(&amount, amount, low))
345+
return 1.0;
346+
if (!amount_msat_sub(&all_states, high, low))
347+
PANIC("we expect high > low");
348+
if (!amount_msat_sub(&good_states, all_states, amount))
349+
PANIC("we expect high > amount");
350+
return amount_msat_ratio(good_states, all_states);
351+
}
352+
353+
/* Returns the probability of success that this channel is able to forward x
354+
* considering only the bounds on the liquidity. */
355+
static double channel_probability(const struct pay_parameters *params,
356+
const struct gossmap_chan *c, const int dir,
357+
struct amount_msat forward)
358+
{
359+
struct amount_msat mincap, maxcap;
360+
/* This takes into account any payments in progress. */
361+
get_constraints(params->rq, c, dir, &mincap, &maxcap);
362+
/* Assume if min > max, min is wrong */
363+
if (amount_msat_greater(mincap, maxcap))
364+
mincap = maxcap;
365+
/* It is preferable to work on 1msat past the known bound. */
366+
if (!amount_msat_accumulate(&maxcap, amount_msat(1)))
367+
PANIC("maxcap + 1msat overflows");
368+
return pickhardt_richter_probability(mincap, maxcap, forward);
369+
}
370+
322371
// TODO(eduardo): unit test this
323372
/* Split a directed channel into parts with linear cost function. */
324373
static void linearize_channel(const struct pay_parameters *params,
@@ -348,9 +397,8 @@ static void linearize_channel(const struct pay_parameters *params,
348397
{
349398
set_capacity(&capacity[i], params->cap_fraction[i]*(b-a), &cap_on_capacity);
350399

351-
cost[i] = params->cost_fraction[i] * 1000
352-
* amount_msat_ratio(params->amount, params->accuracy)
353-
/ (b - a);
400+
cost[i] = params->prob_cost_factor * params->cost_fraction[i] /
401+
(b - a);
354402
}
355403
}
356404

@@ -867,6 +915,46 @@ get_flow_paths(const tal_t *ctx,
867915
return flows;
868916
}
869917

918+
/* Given a single path build a flow set. */
919+
static struct flow **
920+
get_flow_singlepath(const tal_t *ctx, const tal_t *working_ctx,
921+
const struct graph *graph, const struct gossmap *gossmap,
922+
const struct node source, const struct node destination,
923+
const u64 pay_amount, const struct arc *prev)
924+
{
925+
struct flow **flows = tal_arr(ctx, struct flow *, 0);
926+
927+
size_t length = 0;
928+
929+
for (u32 cur_idx = destination.idx; cur_idx != source.idx;) {
930+
assert(cur_idx != INVALID_INDEX);
931+
length++;
932+
struct arc arc = prev[cur_idx];
933+
struct node next = arc_tail(graph, arc);
934+
cur_idx = next.idx;
935+
}
936+
struct flow *f = tal(ctx, struct flow);
937+
f->path = tal_arr(f, const struct gossmap_chan *, length);
938+
f->dirs = tal_arr(f, int, length);
939+
940+
for (u32 cur_idx = destination.idx; cur_idx != source.idx;) {
941+
int chandir;
942+
u32 chanidx;
943+
struct arc arc = prev[cur_idx];
944+
arc_to_parts(arc, &chanidx, &chandir, NULL, NULL);
945+
946+
length--;
947+
f->path[length] = gossmap_chan_byidx(gossmap, chanidx);
948+
f->dirs[length] = chandir;
949+
950+
struct node next = arc_tail(graph, arc);
951+
cur_idx = next.idx;
952+
}
953+
954+
tal_arr_expand(&flows, f);
955+
return flows;
956+
}
957+
870958
// TODO(eduardo): choose some default values for the minflow parameters
871959
/* eduardo: I think it should be clear that this module deals with linear
872960
* flows, ie. base fees are not considered. Hence a flow along a path is
@@ -914,6 +1002,8 @@ struct flow **minflow(const tal_t *ctx,
9141002
/params->cap_fraction[i];
9151003
}
9161004

1005+
params->prob_cost_factor =
1006+
1000 * amount_msat_ratio(params->amount, params->accuracy);
9171007
params->delay_feefactor = delay_feefactor;
9181008
params->base_fee_penalty = base_fee_penalty_estimate(amount);
9191009

@@ -1025,6 +1115,173 @@ static struct amount_msat linear_flows_cost(struct flow **flows,
10251115
return total;
10261116
}
10271117

1118+
/* Initialize the data vectors for the single-path solver. */
1119+
static void init_linear_network_single_path(
1120+
const tal_t *ctx, const struct pay_parameters *params, struct graph **graph,
1121+
double **arc_prob_cost, s64 **arc_fee_cost, s64 **arc_capacity)
1122+
{
1123+
const size_t max_num_chans = gossmap_max_chan_idx(params->rq->gossmap);
1124+
const size_t max_num_arcs = max_num_chans * ARCS_PER_CHANNEL;
1125+
const size_t max_num_nodes = gossmap_max_node_idx(params->rq->gossmap);
1126+
1127+
*graph = graph_new(ctx, max_num_nodes, max_num_arcs, ARC_DUAL_BITOFF);
1128+
*arc_prob_cost = tal_arr(ctx, double, max_num_arcs);
1129+
for (size_t i = 0; i < max_num_arcs; ++i)
1130+
(*arc_prob_cost)[i] = DBL_MAX;
1131+
1132+
*arc_fee_cost = tal_arr(ctx, s64, max_num_arcs);
1133+
for (size_t i = 0; i < max_num_arcs; ++i)
1134+
(*arc_fee_cost)[i] = INT64_MAX;
1135+
*arc_capacity = tal_arrz(ctx, s64, max_num_arcs);
1136+
1137+
/* We use this quantity to compute the cost of routing the entire
1138+
* amount, as opposed to the cost per unit of flow. */
1139+
const u64 pay_amount =
1140+
amount_msat_ratio_ceil(params->amount, params->accuracy);
1141+
1142+
const struct gossmap *gossmap = params->rq->gossmap;
1143+
1144+
for (struct gossmap_node *node = gossmap_first_node(gossmap); node;
1145+
node = gossmap_next_node(gossmap, node)) {
1146+
const u32 node_id = gossmap_node_idx(gossmap, node);
1147+
1148+
for (size_t j = 0; j < node->num_chans; ++j) {
1149+
int half;
1150+
const struct gossmap_chan *c =
1151+
gossmap_nth_chan(gossmap, node, j, &half);
1152+
1153+
if (!gossmap_chan_set(c, half) ||
1154+
!c->half[half].enabled)
1155+
continue;
1156+
1157+
/* If a channel cannot forward the total amount we don't
1158+
* use it. */
1159+
if (amount_msat_less(params->amount,
1160+
gossmap_chan_htlc_min(c, half)) ||
1161+
amount_msat_greater(params->amount,
1162+
gossmap_chan_htlc_max(c, half)))
1163+
continue;
1164+
1165+
const u32 chan_id = gossmap_chan_idx(gossmap, c);
1166+
1167+
const struct gossmap_node *next =
1168+
gossmap_nth_node(gossmap, c, !half);
1169+
1170+
const u32 next_id = gossmap_node_idx(gossmap, next);
1171+
1172+
/* channel to self? */
1173+
if (node_id == next_id)
1174+
continue;
1175+
1176+
struct arc arc =
1177+
arc_from_parts(chan_id, half, 0, false);
1178+
1179+
graph_add_arc(*graph, arc, node_obj(node_id),
1180+
node_obj(next_id));
1181+
1182+
(*arc_capacity)[arc.idx] = 1;
1183+
(*arc_prob_cost)[arc.idx] =
1184+
params->prob_cost_factor * (-1.0) *
1185+
log(channel_probability(params, c, half,
1186+
params->amount));
1187+
1188+
(*arc_fee_cost)[arc.idx] =
1189+
pay_amount *
1190+
linear_fee_cost(c->half[half].base_fee,
1191+
c->half[half].proportional_fee,
1192+
c->half[half].delay,
1193+
params->base_fee_penalty,
1194+
params->delay_feefactor);
1195+
}
1196+
}
1197+
}
1198+
1199+
/* Similar to minflow but computes routes that have a single path. */
1200+
struct flow **single_path_flow(const tal_t *ctx, const struct route_query *rq,
1201+
const struct gossmap_node *source,
1202+
const struct gossmap_node *target,
1203+
struct amount_msat amount, u32 mu,
1204+
double delay_feefactor)
1205+
{
1206+
struct flow **flow_paths;
1207+
/* We allocate everything off this, and free it at the end,
1208+
* as we can be called multiple times without cleaning tmpctx! */
1209+
tal_t *working_ctx = tal(NULL, char);
1210+
struct pay_parameters *params = tal(working_ctx, struct pay_parameters);
1211+
1212+
params->rq = rq;
1213+
params->source = source;
1214+
params->target = target;
1215+
params->amount = amount;
1216+
/* for the single-path solver the accuracy does not detriment
1217+
* performance */
1218+
params->accuracy = AMOUNT_MSAT(1000);
1219+
params->delay_feefactor = delay_feefactor;
1220+
params->base_fee_penalty = base_fee_penalty_estimate(amount);
1221+
params->prob_cost_factor =
1222+
1000 * amount_msat_ratio(params->amount, params->accuracy);
1223+
1224+
struct graph *graph;
1225+
double *arc_prob_cost;
1226+
s64 *arc_fee_cost;
1227+
s64 *arc_capacity;
1228+
1229+
init_linear_network_single_path(working_ctx, params, &graph,
1230+
&arc_prob_cost, &arc_fee_cost,
1231+
&arc_capacity);
1232+
1233+
const struct node dst = {.idx = gossmap_node_idx(rq->gossmap, target)};
1234+
const struct node src = {.idx = gossmap_node_idx(rq->gossmap, source)};
1235+
1236+
const size_t max_num_nodes = graph_max_num_nodes(graph);
1237+
const size_t max_num_arcs = graph_max_num_arcs(graph);
1238+
1239+
s64 *potential = tal_arrz(working_ctx, s64, max_num_nodes);
1240+
s64 *distance = tal_arrz(working_ctx, s64, max_num_nodes);
1241+
s64 *arc_cost = tal_arrz(working_ctx, s64, max_num_arcs);
1242+
struct arc *prev = tal_arrz(working_ctx, struct arc, max_num_nodes);
1243+
1244+
combine_cost_function(working_ctx, graph, arc_prob_cost, arc_fee_cost,
1245+
rq->biases, mu, arc_cost);
1246+
1247+
/* We solve a linear cost flow problem. */
1248+
if (!dijkstra_path(working_ctx, graph, src, dst,
1249+
/* prune = */ true, arc_capacity,
1250+
/*threshold = */ 1, arc_cost, potential, prev,
1251+
distance)) {
1252+
rq_log(tmpctx, rq, LOG_BROKEN,
1253+
"%s: could not find a feasible single path", __func__);
1254+
goto fail;
1255+
}
1256+
const u64 pay_amount =
1257+
amount_msat_ratio_ceil(params->amount, params->accuracy);
1258+
1259+
/* We dissect the flow into payment routes.
1260+
* Actual amounts considering fees are computed for every
1261+
* channel in the routes. */
1262+
flow_paths = get_flow_singlepath(ctx, working_ctx, graph, rq->gossmap,
1263+
src, dst, pay_amount, prev);
1264+
if (!flow_paths) {
1265+
rq_log(tmpctx, rq, LOG_BROKEN,
1266+
"%s: failed to extract flow paths from the single-path "
1267+
"solution",
1268+
__func__);
1269+
goto fail;
1270+
}
1271+
if (tal_count(flow_paths) != 1) {
1272+
rq_log(
1273+
tmpctx, rq, LOG_BROKEN,
1274+
"%s: single-path solution returned a multi route solution",
1275+
__func__);
1276+
goto fail;
1277+
}
1278+
tal_free(working_ctx);
1279+
return flow_paths;
1280+
1281+
fail:
1282+
tal_free(working_ctx);
1283+
return NULL;
1284+
}
10281285

10291286
const char *default_routes(const tal_t *ctx, struct route_query *rq,
10301287
const struct gossmap_node *srcnode,

plugins/askrene/mcf.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,28 @@ struct flow **minflow(const tal_t *ctx,
3434
double delay_feefactor,
3535
bool single_part);
3636

37+
/**
38+
* API for min cost single path.
39+
* @ctx: context to allocate returned flows from
40+
* @rq: the route_query we're processing (for logging)
41+
* @source: the source to start from
42+
* @target: the target to pay
43+
* @amount: the amount we want to reach @target
44+
* @mu: 0 = corresponds to only probabilities, 100 corresponds to only fee.
45+
* @delay_feefactor: convert 1 block delay into msat.
46+
*
47+
* @delay_feefactor converts 1 block delay into msat, as if it were an additional
48+
* fee. So if a CLTV delay on a node is 5 blocks, that's treated as if it
49+
* were a fee of 5 * @delay_feefactor.
50+
*
51+
* Returns an array with one flow which deliver amount to target, or NULL.
52+
*/
53+
struct flow **single_path_flow(const tal_t *ctx, const struct route_query *rq,
54+
const struct gossmap_node *source,
55+
const struct gossmap_node *target,
56+
struct amount_msat amount, u32 mu,
57+
double delay_feefactor);
58+
3759
/* To sanity check: this is the approximation mcf uses for the cost
3860
* of each channel. */
3961
struct amount_msat linear_flow_cost(const struct flow *flow,

0 commit comments

Comments
 (0)