Skip to content

Commit 9d966b4

Browse files
committed
askrene: take into account the reduction in "spendable" with additional HTLCs.
"spendable" is for a single HTLC: if we own the channel, this amount decreases with every HTLC, as we have to pay fees. We have access to this since we call listpeerchannels anyway, so we can calculate the additional costs and use it in the refining phase. Signed-off-by: Rusty Russell <[email protected]>
1 parent 3e96955 commit 9d966b4

File tree

3 files changed

+141
-22
lines changed

3 files changed

+141
-22
lines changed

plugins/askrene/askrene.c

Lines changed: 130 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,47 @@ static struct askrene *get_askrene(struct plugin *plugin)
2727
return plugin_get_data(plugin, struct askrene);
2828
}
2929

30+
/* "spendable" for a channel assumes a single HTLC: for additional HTLCs,
31+
* the need to pay for fees (if we're the owner) reduces it */
32+
struct per_htlc_cost {
33+
struct short_channel_id_dir scidd;
34+
struct amount_msat per_htlc_cost;
35+
};
36+
37+
static const struct short_channel_id_dir *
38+
per_htlc_cost_key(const struct per_htlc_cost *phc)
39+
{
40+
return &phc->scidd;
41+
}
42+
43+
static size_t hash_scidd(const struct short_channel_id_dir *scidd)
44+
{
45+
/* scids cost money to generate, so simple hash works here */
46+
return (scidd->scid.u64 >> 32) ^ (scidd->scid.u64 << 1) ^ scidd->dir;
47+
}
48+
49+
static inline bool per_htlc_cost_eq_key(const struct per_htlc_cost *phc,
50+
const struct short_channel_id_dir *scidd)
51+
{
52+
return short_channel_id_dir_eq(scidd, &phc->scidd);
53+
}
54+
55+
HTABLE_DEFINE_TYPE(struct per_htlc_cost,
56+
per_htlc_cost_key,
57+
hash_scidd,
58+
per_htlc_cost_eq_key,
59+
additional_cost_htable);
60+
61+
/* Get the scidd for the i'th hop in flow */
62+
static void get_scidd(const struct gossmap *gossmap,
63+
const struct flow *flow,
64+
size_t i,
65+
struct short_channel_id_dir *scidd)
66+
{
67+
scidd->scid = gossmap_chan_scid(gossmap, flow->path[i]);
68+
scidd->dir = flow->dirs[i];
69+
}
70+
3071
static bool have_layer(const char **layers, const char *name)
3172
{
3273
for (size_t i = 0; i < tal_count(layers); i++) {
@@ -271,8 +312,7 @@ static void add_reservation(struct reservations *r,
271312
struct askrene *askrene = get_askrene(rq->plugin);
272313
size_t idx;
273314

274-
scidd.scid = gossmap_chan_scid(rq->gossmap, flow->path[i]);
275-
scidd.dir = flow->dirs[i];
315+
get_scidd(rq->gossmap, flow, i, &scidd);
276316

277317
/* This should not happen, but simply don't reserve if it does */
278318
if (!reserves_add(askrene->reserved, &scidd, &amt, 1)) {
@@ -293,7 +333,8 @@ static void add_reservation(struct reservations *r,
293333
tal_arr_expand(&r->amounts, amt);
294334
}
295335

296-
/* We have a basic set of flows, but we need to add fees. This can
336+
/* We have a basic set of flows, but we need to add fees, and take into
337+
* account that "spendable" estimates are for a single HTLC. This can
297338
* push us again over capacity or htlc_maximum_msat.
298339
*
299340
* We may have to reduce the flow amount in response to these.
@@ -317,7 +358,9 @@ static const char *constrain_flow(const tal_t *ctx,
317358
msat = flow->delivers;
318359
for (int i = tal_count(flow->path) - 1; i >= 0; i--) {
319360
const struct half_chan *h = flow_edge(flow, i);
320-
struct amount_msat min, max;
361+
struct amount_msat min, max, amount_to_reserve;
362+
struct short_channel_id_dir scidd;
363+
const struct per_htlc_cost *phc;
321364
const char *max_cause;
322365

323366
/* We can pass constraints due to addition of fees! */
@@ -343,9 +386,19 @@ static const char *constrain_flow(const tal_t *ctx,
343386
why_decreased = max_cause;
344387
}
345388

389+
/* Reserve more for local channels if it reduces capacity */
390+
get_scidd(rq->gossmap, flow, i, &scidd);
391+
phc = additional_cost_htable_get(rq->additional_costs, &scidd);
392+
if (phc) {
393+
if (!amount_msat_add(&amount_to_reserve, msat, phc->per_htlc_cost))
394+
abort();
395+
} else {
396+
amount_to_reserve = msat;
397+
}
398+
346399
/* Reserve it, so if the next flow asks about the same channel,
347400
it will see the reduced capacity from this one. */
348-
add_reservation(reservations, rq, flow, i, msat);
401+
add_reservation(reservations, rq, flow, i, amount_to_reserve);
349402

350403
if (!amount_msat_add_fee(&msat, h->base_fee, h->proportional_fee))
351404
plugin_err(rq->plugin, "Adding fee to amount");
@@ -472,9 +525,11 @@ static void add_to_flow(struct flow *flow,
472525
}
473526
}
474527

475-
/* We got an answer from min-cost-flow, but we now need to add fees.
476-
* This can cause us to hit limits, and even find that some flows are
477-
* impossible. Returns NULL on success, or an error message.*/
528+
/* We got an answer from min-cost-flow, but we now need to add fees,
529+
* and take into account the cost of individual HTLCs (for local
530+
* channels, at least). This can cause us to hit limits, and even
531+
* find that some flows are impossible. Returns NULL on success, or
532+
* an error message.*/
478533
static const char *
479534
refine_with_fees_and_limits(const tal_t *ctx,
480535
struct route_query *rq,
@@ -599,6 +654,7 @@ static const char *get_routes(const tal_t *ctx,
599654
const struct layer *local_layer,
600655
struct route ***routes,
601656
struct amount_msat **amounts,
657+
const struct additional_cost_htable *additional_costs,
602658
double *probability)
603659
{
604660
struct askrene *askrene = get_askrene(plugin);
@@ -621,6 +677,7 @@ static const char *get_routes(const tal_t *ctx,
621677
rq->reserved = askrene->reserved;
622678
rq->layers = tal_arr(rq, const struct layer *, 0);
623679
rq->capacities = tal_dup_talarr(rq, fp16_t, askrene->capacities);
680+
rq->additional_costs = additional_costs;
624681

625682
/* Layers don't have to exist: they might be empty! */
626683
for (size_t i = 0; i < tal_count(layers); i++) {
@@ -841,15 +898,18 @@ void get_constraints(const struct route_query *rq,
841898
}
842899

843900
struct getroutes_info {
901+
struct command *cmd;
844902
struct node_id *source, *dest;
845903
struct amount_msat *amount, *maxfee;
846904
u32 *finalcltv;
847905
const char **layers;
906+
struct additional_cost_htable *additional_costs;
907+
/* Non-NULL if we are told to use "auto.localchans" */
908+
struct layer *local_layer;
848909
};
849910

850911
static struct command_result *do_getroutes(struct command *cmd,
851912
struct gossmap_localmods *localmods,
852-
const struct layer *local_layer,
853913
const struct getroutes_info *info)
854914
{
855915
const char *err;
@@ -861,8 +921,8 @@ static struct command_result *do_getroutes(struct command *cmd,
861921
err = get_routes(cmd, cmd->plugin,
862922
info->source, info->dest,
863923
*info->amount, *info->maxfee, *info->finalcltv,
864-
info->layers, localmods, local_layer,
865-
&routes, &amounts, &probability);
924+
info->layers, localmods, info->local_layer,
925+
&routes, &amounts, info->additional_costs, &probability);
866926
if (err)
867927
return command_fail(cmd, PAY_ROUTE_NOT_FOUND, "%s", err);
868928

@@ -903,17 +963,60 @@ static void add_localchan(struct gossmap_localmods *mods,
903963
u32 fee_proportional,
904964
u32 cltv_delta,
905965
bool enabled,
906-
const char *buf UNUSED,
907-
const jsmntok_t *chantok UNUSED,
908-
struct layer *local_layer)
966+
const char *buf,
967+
const jsmntok_t *chantok,
968+
struct getroutes_info *info)
909969
{
970+
u32 feerate;
971+
const char *opener;
972+
const char *err;
973+
910974
gossmod_add_localchan(mods, self, peer, scidd, htlcmin, htlcmax,
911975
spendable, fee_base, fee_proportional, cltv_delta, enabled,
912-
buf, chantok, local_layer);
976+
buf, chantok, info->local_layer);
977+
978+
/* We also need to know the feerate and opener, so we can calculate per-HTLC cost */
979+
feerate = 0; /* Can be unset on unconfirmed channels */
980+
err = json_scan(tmpctx, buf, chantok,
981+
"{feerate?:{perkw:%},opener:%}",
982+
JSON_SCAN(json_to_u32, &feerate),
983+
JSON_SCAN_TAL(tmpctx, json_strdup, &opener));
984+
if (err) {
985+
plugin_log(info->cmd->plugin, LOG_BROKEN,
986+
"Cannot scan channel for feerate and owner (%s): %.*s",
987+
err, json_tok_full_len(chantok), json_tok_full(buf, chantok));
988+
return;
989+
}
990+
991+
if (feerate != 0 && streq(opener, "local")) {
992+
/* BOLT #3:
993+
* The base fee for a commitment transaction:
994+
* - MUST be calculated to match:
995+
* 1. Start with `weight` = 724 (1124 if `option_anchors` applies).
996+
* 2. For each committed HTLC, if that output is not trimmed as specified in
997+
* [Trimmed Outputs](#trimmed-outputs), add 172 to `weight`.
998+
* 3. Multiply `feerate_per_kw` by `weight`, divide by 1000 (rounding down).
999+
*/
1000+
struct per_htlc_cost *phc
1001+
= tal(info->additional_costs, struct per_htlc_cost);
1002+
1003+
phc->scidd = *scidd;
1004+
if (!amount_sat_to_msat(&phc->per_htlc_cost,
1005+
amount_tx_fee(feerate, 172))) {
1006+
/* Can't happen, since feerate is u32... */
1007+
abort();
1008+
}
1009+
1010+
plugin_log(info->cmd->plugin, LOG_DBG, "Per-htlc cost for %s = %s (%u x 172)",
1011+
fmt_short_channel_id_dir(tmpctx, scidd),
1012+
fmt_amount_msat(tmpctx, phc->per_htlc_cost),
1013+
feerate);
1014+
additional_cost_htable_add(info->additional_costs, phc);
1015+
}
9131016

9141017
/* Known capacity on local channels (ts = max) */
915-
layer_update_constraint(local_layer, scidd, CONSTRAINT_MIN, UINT64_MAX, spendable);
916-
layer_update_constraint(local_layer, scidd, CONSTRAINT_MAX, UINT64_MAX, spendable);
1018+
layer_update_constraint(info->local_layer, scidd, CONSTRAINT_MIN, UINT64_MAX, spendable);
1019+
layer_update_constraint(info->local_layer, scidd, CONSTRAINT_MAX, UINT64_MAX, spendable);
9171020
}
9181021

9191022
static struct command_result *
@@ -922,17 +1025,17 @@ listpeerchannels_done(struct command *cmd,
9221025
const jsmntok_t *toks,
9231026
struct getroutes_info *info)
9241027
{
925-
struct layer *local_layer = new_temp_layer(info, "auto.localchans");
9261028
struct gossmap_localmods *localmods;
9271029

1030+
info->local_layer = new_temp_layer(info, "auto.localchans");
9281031
localmods = gossmods_from_listpeerchannels(cmd,
9291032
&get_askrene(cmd->plugin)->my_id,
9301033
buffer, toks,
9311034
false,
9321035
add_localchan,
933-
local_layer);
1036+
info);
9341037

935-
return do_getroutes(cmd, localmods, local_layer, info);
1038+
return do_getroutes(cmd, localmods, info);
9361039
}
9371040

9381041
static struct command_result *json_getroutes(struct command *cmd,
@@ -951,6 +1054,10 @@ static struct command_result *json_getroutes(struct command *cmd,
9511054
NULL))
9521055
return command_param_failed();
9531056

1057+
info->cmd = cmd;
1058+
info->additional_costs = tal(info, struct additional_cost_htable);
1059+
additional_cost_htable_init(info->additional_costs);
1060+
9541061
if (have_layer(info->layers, "auto.localchans")) {
9551062
struct out_req *req;
9561063

@@ -959,9 +1066,10 @@ static struct command_result *json_getroutes(struct command *cmd,
9591066
listpeerchannels_done,
9601067
forward_error, info);
9611068
return send_outreq(cmd->plugin, req);
962-
}
1069+
} else
1070+
info->local_layer = NULL;
9631071

964-
return do_getroutes(cmd, gossmap_localmods_new(cmd), NULL, info);
1072+
return do_getroutes(cmd, gossmap_localmods_new(cmd), info);
9651073
}
9661074

9671075
static struct command_result *json_askrene_reserve(struct command *cmd,

plugins/askrene/askrene.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#define LIGHTNING_PLUGINS_ASKRENE_ASKRENE_H
33
#include "config.h"
44
#include <bitcoin/short_channel_id.h>
5+
#include <ccan/htable/htable_type.h>
56
#include <ccan/list/list.h>
67
#include <common/amount.h>
78
#include <common/fp16.h>
@@ -47,6 +48,9 @@ struct route_query {
4748

4849
/* Cache of channel capacities for non-reserved, unknown channels. */
4950
fp16_t *capacities;
51+
52+
/* Additional per-htlc cost for local channels */
53+
const struct additional_cost_htable *additional_costs;
5054
};
5155

5256
/* Given a gossmap channel, get the current known min/max */

tests/test_askrene.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,11 +515,18 @@ def test_live_spendable(node_factory, bitcoind):
515515
]
516516

517517
path_total = {}
518+
num_htlcs = {}
518519
for r in routes["routes"]:
519520
key = "{}/{}".format(
520521
r["path"][0]["short_channel_id"], r["path"][0]["direction"]
521522
)
522523
path_total[key] = path_total.get(key, 0) + r["path"][0]["amount_msat"]
524+
num_htlcs[key] = num_htlcs.get(key, 0) + 1
525+
526+
# Take into account 645000msat (3750 feerate x 172 weight) per-HTLC reduction in capacity.
527+
for k in path_total.keys():
528+
if k in maxes:
529+
maxes[k] -= (3750 * 172) * (num_htlcs[k] - 1)
523530

524531
exceeded = {}
525532
for scidd in maxes.keys():

0 commit comments

Comments
 (0)