From d90664c40968b86a2f55e357222418314d8cd8af Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Thu, 11 Jun 2020 11:38:41 +0200 Subject: [PATCH] paymod: Add a deadline to the pay command and retry modifier --- plugins/libplugin-pay.c | 77 +++++++++++++++++++++++++++++++++++++++++ plugins/libplugin-pay.h | 1 + plugins/pay.c | 4 +++ tests/test_pay.py | 2 +- 4 files changed, 83 insertions(+), 1 deletion(-) diff --git a/plugins/libplugin-pay.c b/plugins/libplugin-pay.c index 179fa63b6..b554bec22 100644 --- a/plugins/libplugin-pay.c +++ b/plugins/libplugin-pay.c @@ -36,6 +36,7 @@ struct payment *payment_new(tal_t *ctx, struct command *cmd, /* Re-establish the unmodified constraints for our sub-payment. */ p->constraints = *parent->start_constraints; + p->deadline = parent->deadline; } else { assert(cmd != NULL); p->partid = 0; @@ -1317,10 +1318,21 @@ static inline void retry_step_cb(struct retry_mod_data *rd, { struct payment *subpayment; struct retry_mod_data *rdata = payment_mod_retry_get_data(p); + struct timeabs now = time_now(); if (p->step != PAYMENT_STEP_FAILED) return payment_continue(p); + if (time_after(now, p->deadline)) { + plugin_log( + p->plugin, LOG_INFORM, + "Payment deadline expired, not retrying (partial-)payment " + "%s/%d", + type_to_string(tmpctx, struct sha256, p->payment_hash), + p->partid); + return payment_continue(p); + } + /* If we failed to find a route, it's unlikely we can suddenly find a * new one without any other changes, so it's time to give up. */ if (p->route == NULL) @@ -1928,3 +1940,68 @@ static struct direct_pay_data *direct_pay_init(struct payment *p) REGISTER_PAYMENT_MODIFIER(directpay, struct direct_pay_data *, direct_pay_init, direct_pay_cb); + +static struct command_result *waitblockheight_rpc_cb(struct command *cmd, + const char *buffer, + const jsmntok_t *toks, + struct payment *p) +{ + struct payment *subpayment; + subpayment = payment_new(p, NULL, p, p->modifiers); + payment_start(subpayment); + payment_set_step(p, PAYMENT_STEP_RETRY); + subpayment->why = + tal_fmt(subpayment, "Retrying after waiting for blockchain sync."); + payment_continue(p); + return command_still_pending(cmd); +} + +static void waitblockheight_cb(void *d, struct payment *p) +{ + struct out_req *req; + struct timeabs now = time_now(); + struct timerel remaining; + u32 blockheight; + int failcode; + const u8 *raw_message; + if (p->step != PAYMENT_STEP_FAILED) + return payment_continue(p); + + /* If we don't have an error message to parse we can't wait for blockheight. */ + if (p->result == NULL) + return payment_continue(p); + + if (time_after(now, p->deadline)) + return payment_continue(p); + + failcode = p->result->failcode; + raw_message = p->result->raw_message; + remaining = time_between(p->deadline, now); + + if (failcode != 17 /* Former final_expiry_too_soon */) { + blockheight = p->start_block + 1; + } else { + /* If it's incorrect_or_unknown_payment_details, that tells us + * what height they're at */ + struct amount_msat unused; + const void *ptr = raw_message; + if (!fromwire_incorrect_or_unknown_payment_details( + ptr, &unused, &blockheight)) + return payment_continue(p); + } + + plugin_log(p->plugin, LOG_INFORM, + "Remote node appears to be on a longer chain, which causes " + "CLTV timeouts to be incorrect. Waiting up to %" PRIu64 + " seconds to catch up to block %d before retrying.", + time_to_sec(remaining), blockheight); + + req = jsonrpc_request_start(p->plugin, NULL, "waitblockheight", + waitblockheight_rpc_cb, + waitblockheight_rpc_cb, p); + json_add_u32(req->js, "blockheight", blockheight); + json_add_u32(req->js, "timeout", time_to_sec(remaining)); + send_outreq(p->plugin, req); +} + +REGISTER_PAYMENT_MODIFIER(waitblockheight, void *, NULL, waitblockheight_cb); diff --git a/plugins/libplugin-pay.h b/plugins/libplugin-pay.h index a6945a6ff..2e79790b9 100644 --- a/plugins/libplugin-pay.h +++ b/plugins/libplugin-pay.h @@ -317,6 +317,7 @@ REGISTER_PAYMENT_MODIFIER_HEADER(routehints, struct routehints_data); REGISTER_PAYMENT_MODIFIER_HEADER(exemptfee, struct exemptfee_data); REGISTER_PAYMENT_MODIFIER_HEADER(shadowroute, struct shadow_route_data); REGISTER_PAYMENT_MODIFIER_HEADER(directpay, struct direct_pay_data); +extern struct payment_modifier waitblockheight_pay_mod; /* For the root payment we can seed the channel_hints with the result from * `listpeers`, hence avoid channels that we know have insufficient capacity diff --git a/plugins/pay.c b/plugins/pay.c index 63fef4f48..1faf6940d 100644 --- a/plugins/pay.c +++ b/plugins/pay.c @@ -1842,6 +1842,7 @@ struct payment_modifier *paymod_mods[] = { &shadowroute_pay_mod, &exemptfee_pay_mod, &routehints_pay_mod, + &waitblockheight_pay_mod, &retry_pay_mod, NULL, }; @@ -1861,6 +1862,7 @@ static struct command_result *json_paymod(struct command *cmd, u32 *maxdelay; struct amount_msat *exemptfee, *msat; const char *label; + unsigned int *retryfor; #if DEVELOPER bool *use_shadow; #endif @@ -1876,6 +1878,7 @@ static struct command_result *json_paymod(struct command *cmd, p_opt_def("exemptfee", param_msat, &exemptfee, AMOUNT_MSAT(5000)), p_opt_def("maxdelay", param_number, &maxdelay, maxdelay_default), + p_opt_def("retry_for", param_number, &retryfor, 60), p_opt_def("maxfeepercent", param_millionths, &maxfee_pct_millionths, 500000), #if DEVELOPER @@ -1933,6 +1936,7 @@ static struct command_result *json_paymod(struct command *cmd, p->bolt11 = tal_steal(p, b11str); p->why = "Initial attempt"; p->constraints.cltv_budget = *maxdelay; + p->deadline = timeabs_add(time_now(), time_from_sec(*retryfor)); if (!amount_msat_fee(&p->constraints.fee_budget, p->amount, 0, *maxfee_pct_millionths / 100)) { diff --git a/tests/test_pay.py b/tests/test_pay.py index b7cb37aa4..da37801a6 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -3077,7 +3077,7 @@ def test_pay_modifiers(node_factory): # Make sure that the dummy param is in the help (and therefore assigned to # the modifier data). hlp = l1.rpc.help("paymod")['help'][0] - assert(hlp['command'] == 'paymod bolt11 [msatoshi] [label] [exemptfee] [maxdelay] [maxfeepercent] [use_shadow]') + assert(hlp['command'] == 'paymod bolt11 [msatoshi] [label] [exemptfee] [maxdelay] [retry_for] [maxfeepercent] [use_shadow]') inv = l2.rpc.invoice(123, 'lbl', 'desc')['bolt11'] r = l1.rpc.paymod(inv)