Skip to content

Commit

Permalink
pay: Implement retry in case of final CLTV being too soon for receiver.
Browse files Browse the repository at this point in the history
Changelog-Fixed: Detect a previously non-permanent error
(`final_cltv_too_soon`) that has been merged into a permanent
error (`incorrect_or_unknown_payment_details`), and retry that
failure case in `pay`.
  • Loading branch information
ZmnSCPxj committed Dec 26, 2019
1 parent 78a0827 commit 1545350
Showing 1 changed file with 65 additions and 5 deletions.
70 changes: 65 additions & 5 deletions plugins/pay.c
Original file line number Diff line number Diff line change
Expand Up @@ -361,18 +361,78 @@ handle_payment_error(struct command *cmd,
plugin_err("waitsendpay error gave no 'code'? '%.*s'",
error->end - error->start, buf + error->start);

if (code != PAY_UNPARSEABLE_ONION) {
failcodetok = json_delve(buf, error, ".data.failcode");
if (!json_to_int(buf, failcodetok, &failcode))
plugin_err("waitsendpay error gave no 'failcode'? '%.*s'",
error->end - error->start, buf + error->start);
}

/* Special case for WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS.
*
* One possible trigger for this failure is that the receiver
* thinks the final timeout it gets is too near the future.
*
* For the most part, we respect the indicated `final_cltv`
* in the invoice, and our shadow routing feature also tends
* to give more timing budget to the receiver than the
* `final_cltv`.
*
* However, there is an edge case possible on real networks:
*
* * We send out a payment respecting the `final_cltv` of
* the receiver.
* * Miners mine a new block while the payment is in transit.
* * By the time the payment reaches the receiver, the
* payment violates the `final_cltv` because the receiver
* is now using a different basis blockheight.
*
* This is a transient error.
* Unfortunately, WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS
* is marked with the PERM bit.
* This means that we would give up on this since `waitsendpay`
* would return PAY_DESTINATION_PERM_FAIL instead of
* PAY_TRY_OTHER_ROUTE.
* Thus the `pay` plugin would not retry this case.
*
* Thus, we need to add this special-case checking here, where
* the blockheight when we started the pay attempt was not
* the same as when we got the failure.
*
* In the past this particular failure had its own failure code,
* equivalent to 17.
* In case the receiver is a really old software, we also
* special-case it here.
*
* FIXME: we should actually parse the returned `raw_message`
* field, which contains the `height` (blockheight) that the
* receiver is at, and see if it is greater than the block
* at which we started.
* If so, we should actually wait to achieve at least that
* blockheight before trying the payment again.
* The below catches this in practice unless block propagation
* to your bitcoin node is slow.
*/
if ((code != PAY_UNPARSEABLE_ONION) &&
((failcode == 17) ||
((failcode == WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS) &&
(attempt->start_block != attempt->waitsendpay_err_block))))
return start_pay_attempt(cmd, pc,
"Retrying due to possible "
"blockheight disagreement "
"with receiver: "
"we sent at height %"PRIu32", "
"currently at height %"PRIu32".",
attempt->start_block,
attempt->waitsendpay_err_block);

/* FIXME: Handle PAY_UNPARSEABLE_ONION! */

/* Many error codes are final. */
if (code != PAY_TRY_OTHER_ROUTE) {
return forward_error(cmd, buf, error, pc);
}

failcodetok = json_delve(buf, error, ".data.failcode");
if (!json_to_int(buf, failcodetok, &failcode))
plugin_err("waitsendpay error gave no 'failcode'? '%.*s'",
error->end - error->start, buf + error->start);

if (failcode & NODE) {
nodeidtok = json_delve(buf, error, ".data.erring_node");
if (!nodeidtok)
Expand Down

0 comments on commit 1545350

Please sign in to comment.