Skip to content

Commit

Permalink
xpay: make sure to call preapproveinvoice!
Browse files Browse the repository at this point in the history
This is required for VLS which wants to know (and potentially decline) invoices
we're trying to pay.

As a nice side effect, our "check" command for xpay now does much more thorough
checking of arguments.

Signed-off-by: Rusty Russell <[email protected]>
  • Loading branch information
rustyrussell committed Nov 18, 2024
1 parent 2c15dc0 commit 05fbcb4
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 12 deletions.
54 changes: 42 additions & 12 deletions plugins/xpay/xpay.c
Original file line number Diff line number Diff line change
Expand Up @@ -1380,6 +1380,26 @@ static struct command_result *param_string_array(struct command *cmd, const char
return NULL;
}

static struct command_result *
preapproveinvoice_succeed(struct command *cmd,
const char *method,
const char *buf,
const jsmntok_t *result,
struct payment *payment)
{
struct xpay *xpay = xpay_of(cmd->plugin);

/* Now we can conclude `check` command */
if (command_check_only(cmd)) {
return command_check_done(cmd);
}

payment->unique_id = xpay->counter++;
payment->private_layer = tal_fmt(payment,
"xpay-%"PRIu64, payment->unique_id);
return populate_private_layer(cmd, payment);
}

static struct command_result *json_xpay(struct command *cmd,
const char *buffer,
const jsmntok_t *params)
Expand All @@ -1388,26 +1408,24 @@ static struct command_result *json_xpay(struct command *cmd,
struct amount_msat *msat, *maxfee, *partial;
struct payment *payment = tal(cmd, struct payment);
unsigned int *retryfor;
struct out_req *req;
char *err;

if (!param(cmd, buffer, params,
p_req("invstring", param_invstring, &payment->invstring),
p_opt("amount_msat", param_msat, &msat),
p_opt("maxfee", param_msat, &maxfee),
p_opt("layers", param_string_array, &payment->layers),
p_opt_def("retry_for", param_number, &retryfor, 60),
p_opt("partial_msat", param_msat, &partial),
NULL))
if (!param_check(cmd, buffer, params,
p_req("invstring", param_invstring, &payment->invstring),
p_opt("amount_msat", param_msat, &msat),
p_opt("maxfee", param_msat, &maxfee),
p_opt("layers", param_string_array, &payment->layers),
p_opt_def("retry_for", param_number, &retryfor, 60),
p_opt("partial_msat", param_msat, &partial),
NULL))
return command_param_failed();

list_head_init(&payment->current_attempts);
list_head_init(&payment->past_attempts);
payment->plugin = cmd->plugin;
payment->cmd = cmd;
payment->amount_being_routed = AMOUNT_MSAT(0);
payment->unique_id = xpay->counter++;
payment->private_layer = tal_fmt(payment,
"xpay-%"PRIu64, payment->unique_id);
payment->group_id = pseudorand(INT64_MAX);
payment->total_num_attempts = payment->num_failures = 0;
payment->requests = tal_arr(payment, struct out_req *, 0);
Expand Down Expand Up @@ -1516,7 +1534,19 @@ static struct command_result *json_xpay(struct command *cmd,
} else
payment->maxfee = *maxfee;

return populate_private_layer(cmd, payment);
/* Now preapprove, then start payment. */
if (command_check_only(cmd)) {
req = jsonrpc_request_start(cmd, "check",
&preapproveinvoice_succeed,
&forward_error, payment);
json_add_string(req->js, "command_to_check", "preapproveinvoice");
} else {
req = jsonrpc_request_start(cmd, "preapproveinvoice",
&preapproveinvoice_succeed,
&forward_error, payment);
}
json_add_string(req->js, "bolt11", payment->invstring);
return send_outreq(req);
}

static struct command_result *getchaininfo_done(struct command *aux_cmd,
Expand Down
12 changes: 12 additions & 0 deletions tests/test_xpay.py
Original file line number Diff line number Diff line change
Expand Up @@ -464,3 +464,15 @@ def test_xpay_takeover(node_factory, executor):
inv = l3.rpc.invoice(100000, "test_xpay_takeover13", "test_xpay_takeover13")['bolt11']
l1.rpc.pay(inv)
l1.daemon.wait_for_log('Redirecting pay->xpay')


def test_xpay_preapprove(node_factory):
l1, l2 = node_factory.line_graph(2, opts={'dev-hsmd-fail-preapprove': None})

inv = l2.rpc.invoice(100000, "test_xpay_preapprove", "test_xpay_preapprove")['bolt11']

with pytest.raises(RpcError, match=r"invoice was declined"):
l1.rpc.check('xpay', invstring=inv)

with pytest.raises(RpcError, match=r"invoice was declined"):
l1.rpc.xpay(inv)

0 comments on commit 05fbcb4

Please sign in to comment.