Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions doc/lightningd-config.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,10 @@ command, so they invoices can also be paid onchain.

Setting this makes `xpay` wait until all parts have failed/succeeded before returning. Usually this is unnecessary, as xpay will return on the first success (we have the preimage, if they don't take all the parts that's their problem) or failure (the destination could succeed another part, but it would mean it was only partially paid). The default is `false`.

* **xpay-user-layer**=*name* [plugin `xpay`]

Specify the name of a layer `xpay` shall use always for every payment. This is specially useful when combined with `xpay-handle-pay` since the `layers` parameter is not available in the `pay` interface. This can be specified multiple times to add more layers.

* **askrene-timeout**=*SECONDS* [plugin `askrene`, *dynamic*]

This option makes the `getroutes` call fail if it takes more than this many seconds. Setting it to zero is a fun way to ensure your node never makes payments.
Expand Down
20 changes: 20 additions & 0 deletions plugins/libplugin.c
Original file line number Diff line number Diff line change
Expand Up @@ -1688,6 +1688,14 @@ char *charp_option(struct command *cmd, const char *arg, bool check_only, char *
return NULL;
}

char *multi_string_option(struct command *cmd, const char *arg, bool check_only,
const char ***arr)
{
if (!check_only)
tal_arr_expand(arr, tal_strdup(*arr, arg));
return NULL;
}

bool u64_jsonfmt(struct command *cmd, struct json_stream *js, const char *fieldname, u64 *i)
{
json_add_u64(js, fieldname, *i);
Expand Down Expand Up @@ -1720,6 +1728,18 @@ bool charp_jsonfmt(struct command *cmd, struct json_stream *js, const char *fiel
return true;
}

bool string_array_jsonfmt(struct command *cmd, struct json_stream *js,
const char *fieldname, const char ***arr)
{
if (tal_count(*arr) == 0)
return false;
json_array_start(js, fieldname);
for (size_t i = 0; i < tal_count(*arr); i++)
json_add_string(js, NULL, (*arr)[i]);
json_array_end(js);
return true;
}

bool flag_jsonfmt(struct command *cmd, struct json_stream *js, const char *fieldname, bool *i)
{
/* Don't print if the default (false) */
Expand Down
4 changes: 4 additions & 0 deletions plugins/libplugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,8 @@ char *u32_option(struct command *cmd, const char *arg, bool check_only, u32 *i);
char *u16_option(struct command *cmd, const char *arg, bool check_only, u16 *i);
char *bool_option(struct command *cmd, const char *arg, bool check_only, bool *i);
char *charp_option(struct command *cmd, const char *arg, bool check_only, char **p);
char *multi_string_option(struct command *cmd, const char *arg, bool check_only,
const char ***arr);
char *flag_option(struct command *cmd, const char *arg, bool check_only, bool *i);

bool u64_jsonfmt(struct command *cmd, struct json_stream *js, const char *fieldname,
Expand All @@ -650,6 +652,8 @@ bool bool_jsonfmt(struct command *cmd, struct json_stream *js, const char *field
bool *i);
bool charp_jsonfmt(struct command *cmd, struct json_stream *js, const char *fieldname,
char **p);
bool string_array_jsonfmt(struct command *cmd, struct json_stream *js,
const char *fieldname, const char ***arr);

/* Usually equivalent to NULL, since flag must default to false be useful! */
bool flag_jsonfmt(struct command *cmd, struct json_stream *js, const char *fieldname,
Expand Down
7 changes: 7 additions & 0 deletions plugins/xpay/xpay.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ struct xpay {
bool slow_mode;
/* Suppress calls to askrene-age */
bool dev_no_age;
const char **user_layers;
};

static struct xpay *xpay_of(struct plugin *plugin)
Expand Down Expand Up @@ -1467,6 +1468,8 @@ static struct command_result *getroutes_for(struct command *aux_cmd,
/* Add user-specified layers */
for (size_t i = 0; i < tal_count(payment->layers); i++)
json_add_string(req->js, NULL, payment->layers[i]);
for (size_t i = 0; i < tal_count(xpay->user_layers); i++)
json_add_string(req->js, NULL, xpay->user_layers[i]);
if (payment->disable_mpp)
json_add_string(req->js, NULL, "auto.no_mpp_support");
json_array_end(req->js);
Expand Down Expand Up @@ -2534,6 +2537,7 @@ int main(int argc, char *argv[])
xpay->take_over_pay = false;
xpay->slow_mode = false;
xpay->dev_no_age = false;
xpay->user_layers = tal_arr(xpay, const char *, 0);
plugin_main(argv, init, take(xpay),
PLUGIN_RESTARTABLE, true, NULL,
commands, ARRAY_SIZE(commands),
Expand All @@ -2546,6 +2550,9 @@ int main(int argc, char *argv[])
plugin_option_dynamic("xpay-slow-mode", "bool",
"Wait until all parts have completed before returning success or failure",
bool_option, bool_jsonfmt, &xpay->slow_mode),
plugin_option_multi("xpay-user-layer", "string",
"Add a layer that will be used for every payment",
multi_string_option, string_array_jsonfmt, &xpay->user_layers),
plugin_option_dev("dev-xpay-no-age", "flag",
"Don't call askrene-age",
flag_option, flag_jsonfmt, &xpay->dev_no_age),
Expand Down
23 changes: 2 additions & 21 deletions tests/plugins/test_libplugin.c
Original file line number Diff line number Diff line change
Expand Up @@ -372,25 +372,6 @@ static const struct plugin_notification notifs[] = { {
}
};

static char *set_multi_string_option(struct command *cmd,
const char *arg,
bool check_only,
const char ***arr)
{
if (!check_only)
tal_arr_expand(arr, tal_strdup(*arr, arg));
return NULL;
}

static bool multi_string_jsonfmt(struct command *cmd, struct json_stream *js, const char *fieldname, const char ***arr)
{
json_array_start(js, fieldname);
for (size_t i = 0; i < tal_count(*arr); i++)
json_add_string(js, NULL, (*arr)[i]);
json_array_end(js);
return true;
}

int main(int argc, char *argv[])
{
setup_locale();
Expand Down Expand Up @@ -436,8 +417,8 @@ int main(int argc, char *argv[])
plugin_option_multi("multiopt",
"string",
"Set me multiple times!",
set_multi_string_option,
multi_string_jsonfmt,
multi_string_option,
string_array_jsonfmt,
&tlp->strarr),
NULL);
}
56 changes: 56 additions & 0 deletions tests/test_xpay.py
Original file line number Diff line number Diff line change
Expand Up @@ -1081,3 +1081,59 @@ def mock_getblockhash(req):
# Now let it catch up, and it will retry, and succeed.
l1.daemon.rpcproxy.mock_rpc('getblockhash')
fut.result(TIMEOUT)


def test_xpay_user_layers(node_factory):
l1, l2, l3, l4 = node_factory.get_nodes(
4, opts={"may_reconnect": True, "xpay-handle-pay": True}
)
node_factory.join_nodes([l1, l2, l3], wait_for_announce=True)
node_factory.join_nodes([l2, l4], wait_for_announce=True)

layer = "disable-chan23"
l1.rpc.askrene_create_layer(layer=layer, persistent=True)
scid = l2.rpc.listpeerchannels(l3.info["id"])["channels"][0]["short_channel_id"]
direction = l2.rpc.listpeerchannels(l3.info["id"])["channels"][0]["direction"]
l1.rpc.askrene_update_channel(
layer=layer, enabled=False, short_channel_id_dir=f"{scid}/{direction}"
)

layer = "disable-chan24"
l1.rpc.askrene_create_layer(layer=layer, persistent=True)
scid = l2.rpc.listpeerchannels(l4.info["id"])["channels"][0]["short_channel_id"]
direction = l2.rpc.listpeerchannels(l4.info["id"])["channels"][0]["direction"]
l1.rpc.askrene_update_channel(
layer=layer, enabled=False, short_channel_id_dir=f"{scid}/{direction}"
)

# Let us load these layers as user layers in xpay. Both payments should fail
l1.stop()
l1.daemon.opts["xpay-user-layer"] = ["disable-chan23", "disable-chan24"]
l1.start()
l1.rpc.connect(l2.info["id"], "localhost", l2.port)
l1.daemon.wait_for_log(f"channeld.*: billboard: Channel ready for use")
inv3 = l3.rpc.invoice(1000, "test-xpay-user-layer", "test-xpay-user-layer")[
"bolt11"
]
with pytest.raises(
RpcError,
match="We could not find a usable set of paths. The destination has disabled 1 of 1 channels",
):
l1.rpc.pay(inv3)
inv4 = l4.rpc.invoice(1000, "test-xpay-user-layer", "test-xpay-user-layer")[
"bolt11"
]
with pytest.raises(
RpcError,
match="We could not find a usable set of paths. The destination has disabled 1 of 1 channels",
):
l1.rpc.pay(inv4)

# Without those layers, the same payments should go through
l1.stop()
del l1.daemon.opts["xpay-user-layer"]
l1.start()
l1.rpc.connect(l2.info["id"], "localhost", l2.port)
l1.daemon.wait_for_log(f"channeld.*: billboard: Channel ready for use")
l1.rpc.pay(inv3)
l1.rpc.pay(inv4)
Loading