Skip to content

Commit add398f

Browse files
committed
xpay: wait, if final node gives us an indication we're behind on blockheight.
This doesn't happen much in real life, but it's certainly possible, so do what pay does here. Signed-off-by: Rusty Russell <[email protected]> Fixes: #8612 Changelog-Added: `xpay` will now wait if it suspects a payment failure is due to a height disagreement with the final node.
1 parent 4dca8cf commit add398f

File tree

2 files changed

+80
-4
lines changed

2 files changed

+80
-4
lines changed

plugins/xpay/xpay.c

Lines changed: 80 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ struct payment {
6161
struct plugin *plugin;
6262
/* Stop sending new payments after this */
6363
struct timemono deadline;
64+
/* Blockheight when we started (if in future, wait for this!) */
65+
u32 start_blockheight;
6466
/* This is the command which is expecting the success/fail. When
6567
* it's NULL, that means we're just cleaning up */
6668
struct command *cmd;
@@ -631,6 +633,19 @@ static void outgoing_notify_failure(const struct attempt *attempt,
631633
plugin_notification_end(attempt->payment->plugin, js);
632634
}
633635

636+
/* Extract blockheight from the error */
637+
static u32 error_blockheight(const u8 *errmsg)
638+
{
639+
struct amount_msat htlc_msat;
640+
u32 height;
641+
642+
if (!fromwire_incorrect_or_unknown_payment_details(errmsg,
643+
&htlc_msat,
644+
&height))
645+
return 0;
646+
return height;
647+
}
648+
634649
static void update_knowledge_from_error(struct command *aux_cmd,
635650
const char *buf,
636651
const jsmntok_t *error,
@@ -765,14 +780,24 @@ static void update_knowledge_from_error(struct command *aux_cmd,
765780
index--;
766781
goto strange_error;
767782

768-
case WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS:
769-
/* FIXME: Maybe this was actually a height
770-
* disagreement, so check height */
783+
case WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS: {
784+
struct xpay *xpay = xpay_of(attempt->payment->plugin);
785+
u32 blockheight = error_blockheight(replymsg);
786+
if (blockheight > attempt->payment->start_blockheight) {
787+
attempt_log(attempt, LOG_INFORM,
788+
"Destination failed and said their blockheight was %u (we're at %u): waiting",
789+
blockheight, xpay->blockheight);
790+
/* This will make the next attempt wait. */
791+
attempt->payment->start_blockheight = blockheight;
792+
return;
793+
}
794+
771795
payment_give_up(aux_cmd, attempt->payment,
772796
PAY_DESTINATION_PERM_FAIL,
773797
"Destination said it doesn't know invoice: %s",
774798
errmsg);
775799
return;
800+
}
776801

777802
case WIRE_MPP_TIMEOUT:
778803
/* Not actually an error at all, nothing to do. */
@@ -1315,6 +1340,35 @@ static struct command_result *getroutes_done_err(struct command *aux_cmd,
13151340
return command_still_pending(aux_cmd);
13161341
}
13171342

1343+
static struct command_result *waitblockheight_done(struct command *aux_cmd,
1344+
const char *method,
1345+
const char *buf,
1346+
const jsmntok_t *result,
1347+
struct payment *payment)
1348+
{
1349+
/* Kick off however much is outstanding */
1350+
struct amount_msat needs_routing;
1351+
1352+
if (!amount_msat_sub(&needs_routing,
1353+
payment->amount,
1354+
total_being_sent(payment)))
1355+
abort();
1356+
return getroutes_for(aux_cmd, payment, needs_routing);
1357+
}
1358+
1359+
static struct command_result *waitblockheight_failed(struct command *aux_cmd,
1360+
const char *method,
1361+
const char *buf,
1362+
const jsmntok_t *result,
1363+
struct payment *payment)
1364+
{
1365+
payment_give_up(aux_cmd, payment, PAY_UNSPECIFIED_ERROR,
1366+
"Timed out waiting for blockheight %u. %s",
1367+
payment->start_blockheight,
1368+
payment->prior_results);
1369+
return command_still_pending(aux_cmd);
1370+
}
1371+
13181372
static struct command_result *getroutes_for(struct command *aux_cmd,
13191373
struct payment *payment,
13201374
struct amount_msat deliver)
@@ -1345,6 +1399,28 @@ static struct command_result *getroutes_for(struct command *aux_cmd,
13451399
return do_inject(aux_cmd, attempt);
13461400
}
13471401

1402+
/* Failure message indicated a blockheight difference. */
1403+
if (payment->start_blockheight > xpay->blockheight) {
1404+
struct timemono now = time_mono();
1405+
u64 seconds;
1406+
1407+
if (time_greater_(now.ts, payment->deadline.ts))
1408+
seconds = 0;
1409+
else
1410+
seconds = time_to_sec(timemono_between(payment->deadline, now));
1411+
1412+
payment_log(payment, LOG_UNUSUAL,
1413+
"Our blockheight may be too low: waiting %"PRIu64" seconds for height %u (we are at %u)",
1414+
seconds, payment->start_blockheight, xpay->blockheight);
1415+
req = jsonrpc_request_start(aux_cmd, "waitblockheight",
1416+
waitblockheight_done,
1417+
waitblockheight_failed,
1418+
payment);
1419+
json_add_u32(req->js, "blockheight", payment->start_blockheight);
1420+
json_add_u64(req->js, "timeout", seconds);
1421+
return send_payment_req(aux_cmd, payment, req);
1422+
}
1423+
13481424
if (!amount_msat_sub(&maxfee, payment->maxfee, total_fees_being_sent(payment))) {
13491425
payment_log(payment, LOG_BROKEN, "more fees (%s) in flight than allowed (%s)!",
13501426
fmt_amount_msat(tmpctx, total_fees_being_sent(payment)),
@@ -1872,6 +1948,7 @@ static struct command_result *xpay_core(struct command *cmd,
18721948
payment->prior_results = tal_strdup(payment, "");
18731949
payment->deadline = timemono_add(time_mono(), time_from_sec(retryfor));
18741950
payment->start_time = time_now();
1951+
payment->start_blockheight = xpay->blockheight;
18751952
payment->pay_compat = as_pay;
18761953
payment->invstring = tal_strdup(payment, invstring);
18771954
if (layers)

tests/test_xpay.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1020,7 +1020,6 @@ def test_xpay_bip353(node_factory):
10201020
l2.rpc.xpay('[email protected]', 100)
10211021

10221022

1023-
@pytest.mark.xfail(strict=True)
10241023
def test_xpay_blockheight_mismatch(node_factory, bitcoind, executor):
10251024
"""We should wait a (reasonable) amount if the final node gives us a blockheight that would explain our failure."""
10261025
l1, l2, l3 = node_factory.line_graph(3, wait_for_announce=True)

0 commit comments

Comments
 (0)