Skip to content

Commit b5ef54a

Browse files
committed
askrene: add optional layers to reservations.
We have the issue of aliases: xpay uses scids like 0x0x0 for routehints and blinded paths, and then can apply reservations to them. But generally, reservations are *global*, so we need to differentiate. Changelog-Added: Plugins: `askrene-reserve` and `askrene-unreserve` can take an optional `layer` inside `path` elements. Signed-off-by: Rusty Russell <[email protected]>
1 parent ea0d86a commit b5ef54a

File tree

9 files changed

+90
-18
lines changed

9 files changed

+90
-18
lines changed

doc/schemas/askrene-inform-channel.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"layer": {
2020
"type": "string",
2121
"description": [
22-
"The name of the layer to apply this change to."
22+
"The name of the layer to apply this change to. Also causes us to consider any reservations which are local to that layer (which is useful for fake channels where `layer` is set in an `askrene-reserve` `path` object)."
2323
]
2424
},
2525
"short_channel_id_dir": {

doc/schemas/askrene-reserve.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@
3636
"description": [
3737
"The amount to send into this hop."
3838
]
39+
},
40+
"layer": {
41+
"added": "v25.12",
42+
"type": "string",
43+
"description": [
44+
"The layer to restrict this reservation to. This is only useful for fake channels which are not uniquely identified by `short_channel_id_dir`, which would otherwise confuse multiple unrelated callers."
45+
]
3946
}
4047
}
4148
}

doc/schemas/askrene-unreserve.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@
3636
"description": [
3737
"The amount to send into this hop."
3838
]
39+
},
40+
"layer": {
41+
"added": "v25.12",
42+
"type": "string",
43+
"description": [
44+
"The layer to restrict this reservation to (useful for fake channels)."
45+
]
3946
}
4047
}
4148
}

plugins/askrene/askrene.c

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -147,12 +147,21 @@ static struct command_result *parse_reserve_hop(struct command *cmd,
147147
struct reserve_hop *rhop)
148148
{
149149
const char *err;
150+
const char *layername = NULL;
150151

151-
err = json_scan(tmpctx, buffer, tok, "{short_channel_id_dir:%,amount_msat:%}",
152+
err = json_scan(tmpctx, buffer, tok, "{short_channel_id_dir:%,amount_msat:%,layer?:%}",
152153
JSON_SCAN(json_to_short_channel_id_dir, &rhop->scidd),
153-
JSON_SCAN(json_to_msat, &rhop->amount));
154+
JSON_SCAN(json_to_msat, &rhop->amount),
155+
JSON_SCAN_TAL(tmpctx, json_strdup, &layername));
154156
if (err)
155157
return command_fail_badparam(cmd, name, buffer, tok, err);
158+
if (layername) {
159+
rhop->layer = find_layer(get_askrene(cmd->plugin), layername);
160+
if (!rhop->layer)
161+
return command_fail_badparam(cmd, name, buffer, tok, "Unknown layer");
162+
} else
163+
rhop->layer = NULL;
164+
156165
return NULL;
157166
}
158167

@@ -548,8 +557,8 @@ void get_constraints(const struct route_query *rq,
548557
*max = gossmap_chan_get_capacity(rq->gossmap, chan);
549558

550559
/* Finally, if any is in use, subtract that! */
551-
reserve_sub(rq->reserved, &scidd, min);
552-
reserve_sub(rq->reserved, &scidd, max);
560+
reserve_sub(rq->reserved, &scidd, rq->layers, min);
561+
reserve_sub(rq->reserved, &scidd, rq->layers, max);
553562
}
554563

555564
static struct command_result *do_getroutes(struct command *cmd,
@@ -916,9 +925,11 @@ static struct command_result *json_askrene_unreserve(struct command *cmd,
916925
for (size_t i = 0; i < tal_count(path); i++) {
917926
if (!reserve_remove(askrene->reserved, &path[i])) {
918927
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
919-
"Unknown reservation for %s",
928+
"Unknown reservation for %s%s%s",
920929
fmt_short_channel_id_dir(tmpctx,
921-
&path[i].scidd));
930+
&path[i].scidd),
931+
path[i].layer ? " on layer " : "",
932+
path[i].layer ? layer_name(path[i].layer) : "");
922933
}
923934
}
924935

@@ -933,14 +944,15 @@ static struct command_result *json_askrene_listreservations(struct command *cmd,
933944
struct askrene *askrene = get_askrene(cmd->plugin);
934945
struct json_stream *response;
935946

947+
/* FIXME: We could allow layer names here? */
936948
if (!param(cmd, buffer, params,
937949
NULL))
938950
return command_param_failed();
939951
plugin_log(cmd->plugin, LOG_TRACE, "%s called: %.*s", __func__,
940952
json_tok_full_len(params), json_tok_full(buffer, params));
941953

942954
response = jsonrpc_stream_success(cmd);
943-
json_add_reservations(response, askrene->reserved, "reservations");
955+
json_add_reservations(response, askrene->reserved, "reservations", NULL);
944956
return command_finished(cmd, response);
945957
}
946958

@@ -1065,7 +1077,7 @@ static struct command_result *json_askrene_inform_channel(struct command *cmd,
10651077
case INFORM_CONSTRAINED:
10661078
/* It didn't pass, so minimal assumption is that reserve was all used
10671079
* then there we were one msat short. */
1068-
if (!reserve_accumulate(askrene->reserved, scidd, amount))
1080+
if (!reserve_accumulate(askrene->reserved, scidd, layer, amount))
10691081
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
10701082
"Amount overflow with reserves");
10711083
if (!amount_msat_deduct(amount, AMOUNT_MSAT(1)))

plugins/askrene/explain_failure.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ static const char *why_max_constrained(const tal_t *ctx,
6060
fmt_amount_msat(tmpctx, max));
6161
}
6262

63-
reservations = fmt_reservations(tmpctx, rq->reserved, scidd);
63+
reservations = fmt_reservations(tmpctx, rq->reserved, scidd, rq->layers);
6464
if (reservations) {
6565
if (!ret)
6666
ret = tal_strdup(ctx, "");

plugins/askrene/refine.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ static void add_reservation(struct reserve_hop **reservations,
7272
}
7373
rhop.scidd = *scidd;
7474
rhop.amount = amt;
75+
/* We don't have to restrict it to a layer, since it's transitory:
76+
* nobody else will see this. */
77+
rhop.layer = NULL;
7578
reserve_add(askrene->reserved, &rhop, rq->cmd->id);
7679

7780
/* Set capacities entry to 0 so it get_constraints() looks in reserve. */

plugins/askrene/reserve.c

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ struct reserve_htable *new_reserve_htable(const tal_t *ctx)
4040
return new_htable(ctx, reserve_htable);
4141
}
4242

43+
static void destroy_reserve(struct reserve *r, struct reserve_htable *reserved)
44+
{
45+
if (!reserve_htable_del(reserved, r))
46+
abort();
47+
}
48+
4349
void reserve_add(struct reserve_htable *reserved,
4450
const struct reserve_hop *rhop,
4551
const char *cmd_id TAKES)
@@ -49,6 +55,11 @@ void reserve_add(struct reserve_htable *reserved,
4955
r->timestamp = time_mono();
5056
r->cmd_id = tal_strdup(r, cmd_id);
5157

58+
/* If owned by a layer, clean up when layer destroyed */
59+
if (rhop->layer) {
60+
tal_steal(rhop->layer, r);
61+
tal_add_destructor2(r, destroy_reserve, reserved);
62+
}
5263
reserve_htable_add(reserved, r);
5364
}
5465

@@ -65,8 +76,12 @@ bool reserve_remove(struct reserve_htable *reserved,
6576
r = reserve_htable_getnext(reserved, &rhop->scidd, &rit)) {
6677
if (!amount_msat_eq(r->rhop.amount, rhop->amount))
6778
continue;
79+
if (r->rhop.layer != rhop->layer)
80+
continue;
6881

69-
reserve_htable_del(reserved, r);
82+
/* hops on layers have a destructor which does this. */
83+
if (r->rhop.layer == NULL)
84+
reserve_htable_del(reserved, r);
7085
tal_free(r);
7186
return true;
7287
}
@@ -93,8 +108,18 @@ void reserves_clear_capacities(struct reserve_htable *reserved,
93108
}
94109
}
95110

111+
static bool layer_in(const struct layer *layer,
112+
const struct layer **layers)
113+
{
114+
for (size_t i = 0; i < tal_count(layers); i++)
115+
if (layer == layers[i])
116+
return true;
117+
return false;
118+
}
119+
96120
void reserve_sub(const struct reserve_htable *reserved,
97121
const struct short_channel_id_dir *scidd,
122+
const struct layer **layers,
98123
struct amount_msat *amount)
99124
{
100125
struct reserve *r;
@@ -103,13 +128,16 @@ void reserve_sub(const struct reserve_htable *reserved,
103128
for (r = reserve_htable_getfirst(reserved, scidd, &rit);
104129
r;
105130
r = reserve_htable_getnext(reserved, scidd, &rit)) {
131+
if (r->rhop.layer && !layer_in(r->rhop.layer, layers))
132+
continue;
106133
if (!amount_msat_deduct(amount, r->rhop.amount))
107134
*amount = AMOUNT_MSAT(0);
108135
}
109136
}
110137

111138
bool reserve_accumulate(const struct reserve_htable *reserved,
112139
const struct short_channel_id_dir *scidd,
140+
const struct layer *layer,
113141
struct amount_msat *amount)
114142
{
115143
struct reserve *r;
@@ -118,6 +146,10 @@ bool reserve_accumulate(const struct reserve_htable *reserved,
118146
for (r = reserve_htable_getfirst(reserved, scidd, &rit);
119147
r;
120148
r = reserve_htable_getnext(reserved, scidd, &rit)) {
149+
/* Non-layer ones always get counted. Layered ones have
150+
* to match this layer. */
151+
if (r->rhop.layer && r->rhop.layer != layer)
152+
continue;
121153
if (!amount_msat_add(amount, *amount, r->rhop.amount))
122154
return false;
123155
}
@@ -126,7 +158,8 @@ bool reserve_accumulate(const struct reserve_htable *reserved,
126158

127159
void json_add_reservations(struct json_stream *js,
128160
const struct reserve_htable *reserved,
129-
const char *fieldname)
161+
const char *fieldname,
162+
const struct layer **layers)
130163
{
131164
struct reserve *r;
132165
struct reserve_htable_iter rit;
@@ -135,6 +168,8 @@ void json_add_reservations(struct json_stream *js,
135168
for (r = reserve_htable_first(reserved, &rit);
136169
r;
137170
r = reserve_htable_next(reserved, &rit)) {
171+
if (r->rhop.layer && !layer_in(r->rhop.layer, layers))
172+
continue;
138173
json_object_start(js, NULL);
139174
json_add_short_channel_id_dir(js,
140175
"short_channel_id_dir",
@@ -152,7 +187,8 @@ void json_add_reservations(struct json_stream *js,
152187

153188
const char *fmt_reservations(const tal_t *ctx,
154189
const struct reserve_htable *reserved,
155-
const struct short_channel_id_dir *scidd)
190+
const struct short_channel_id_dir *scidd,
191+
const struct layer **layers)
156192
{
157193
struct reserve *r;
158194
struct reserve_htable_iter rit;
@@ -162,6 +198,8 @@ const char *fmt_reservations(const tal_t *ctx,
162198
r;
163199
r = reserve_htable_getnext(reserved, scidd, &rit)) {
164200
u64 seconds;
201+
if (r->rhop.layer && !layer_in(r->rhop.layer, layers))
202+
continue;
165203
if (!ret)
166204
ret = tal_strdup(ctx, "");
167205
else

plugins/askrene/reserve.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ struct reserve_htable *new_reserve_htable(const tal_t *ctx);
1414
struct reserve_hop {
1515
struct short_channel_id_dir scidd;
1616
struct amount_msat amount;
17+
/* If non-NULL, only applies to this layer */
18+
const struct layer *layer;
1719
};
1820

19-
/* Add a reservation */
21+
/* Add a reservation. */
2022
void reserve_add(struct reserve_htable *reserved,
2123
const struct reserve_hop *rhop,
2224
const char *cmd_id TAKES);
@@ -33,21 +35,25 @@ void reserves_clear_capacities(struct reserve_htable *reserved,
3335
/* Subtract any reserves for scidd from this amount */
3436
void reserve_sub(const struct reserve_htable *reserved,
3537
const struct short_channel_id_dir *scidd,
38+
const struct layer **layers,
3639
struct amount_msat *amount);
3740

3841
/* Add any reserves for scidd to this amount */
3942
bool reserve_accumulate(const struct reserve_htable *reserved,
4043
const struct short_channel_id_dir *scidd,
44+
const struct layer *layer,
4145
struct amount_msat *amount);
4246

4347
/* To explain why we couldn't route */
4448
const char *fmt_reservations(const tal_t *ctx,
4549
const struct reserve_htable *reserved,
46-
const struct short_channel_id_dir *scidd);
50+
const struct short_channel_id_dir *scidd,
51+
const struct layer **layers);
4752

4853
/* Print out a json object for all reservations */
4954
void json_add_reservations(struct json_stream *js,
5055
const struct reserve_htable *reserved,
51-
const char *fieldname);
56+
const char *fieldname,
57+
const struct layer **layers);
5258

5359
#endif /* LIGHTNING_PLUGINS_ASKRENE_RESERVE_H */

tests/test_askrene.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1847,7 +1847,6 @@ def test_askrene_timeout(node_factory, bitcoind):
18471847
final_cltv=5)
18481848

18491849

1850-
@pytest.mark.xfail(strict=True)
18511850
def test_askrene_reserve_clash(node_factory, bitcoind):
18521851
"""Reserves get (erroneously) counted globally by scid, even for fake scids."""
18531852
l1 = node_factory.get_node()
@@ -1897,7 +1896,7 @@ def test_askrene_reserve_clash(node_factory, bitcoind):
18971896

18981897
l1.rpc.askrene_reserve(path=[{'short_channel_id_dir': '0x0x0/1',
18991898
'amount_msat': 950000,
1900-
# 'layer': 'layer1'
1899+
'layer': 'layer1'
19011900
}])
19021901

19031902
# We can't use this on layer 1 anymore, only 50000 msat left.

0 commit comments

Comments
 (0)