From 1e4adb0359471b48e024177163589fab380bb699 Mon Sep 17 00:00:00 2001 From: ZmnSCPxj Date: Sun, 4 Mar 2018 05:35:37 +0000 Subject: [PATCH] pay: Make sendpay nonblocking. --- lightningd/jsonrpc_errors.h | 4 +- lightningd/pay.c | 262 ++++++++++++++++++++++++------------ lightningd/pay.h | 24 +++- lightningd/payalgo.c | 16 ++- 4 files changed, 214 insertions(+), 92 deletions(-) diff --git a/lightningd/jsonrpc_errors.h b/lightningd/jsonrpc_errors.h index 060cb90ec..6ead2d26a 100644 --- a/lightningd/jsonrpc_errors.h +++ b/lightningd/jsonrpc_errors.h @@ -10,7 +10,7 @@ #define JSONRPC2_METHOD_NOT_FOUND -32601 #define JSONRPC2_INVALID_PARAMS -32602 -/* Errors from `pay` and `sendpay` commands */ +/* Errors from `pay`, `sendpay`, or `waitsendpay` commands */ #define PAY_IN_PROGRESS 200 #define PAY_RHASH_ALREADY_USED 201 #define PAY_UNPARSEABLE_ONION 202 @@ -19,5 +19,7 @@ #define PAY_ROUTE_NOT_FOUND 205 #define PAY_ROUTE_TOO_EXPENSIVE 206 #define PAY_INVOICE_EXPIRED 207 +#define PAY_NO_SUCH_PAYMENT 208 +#define PAY_UNSPECIFIED_ERROR 209 #endif /* !defined (LIGHTNING_LIGHTNINGD_JSONRPC_ERRORS_H) */ diff --git a/lightningd/pay.c b/lightningd/pay.c index e18a138ed..fe271018a 100644 --- a/lightningd/pay.c +++ b/lightningd/pay.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -37,12 +38,12 @@ static void destroy_sendpay_command(struct sendpay_command *pc) /* Owned by cxt; if cxt is deleted, then cb will * no longer be called. */ -static struct sendpay_command * -new_sendpay_command(const tal_t *cxt, - const struct sha256 *payment_hash, - struct lightningd *ld, - void (*cb)(const struct sendpay_result *, void*), - void *cbarg) +static void +add_payment_waiter(const tal_t *cxt, + const struct sha256 *payment_hash, + struct lightningd *ld, + void (*cb)(const struct sendpay_result *, void*), + void *cbarg) { struct sendpay_command *pc = tal(cxt, struct sendpay_command); @@ -51,7 +52,6 @@ new_sendpay_command(const tal_t *cxt, pc->cbarg = cbarg; list_add(&ld->sendpay_commands, &pc->list); tal_add_destructor(pc, destroy_sendpay_command); - return pc; } /* Caller responsible for freeing ctx. */ @@ -149,37 +149,6 @@ sendpay_result_simple_fail(const tal_t *ctx, return result; } -/* Immediately fail during send_payment call. */ -static void sendpay_fail_now(void (*cb)(const struct sendpay_result *, void*), - void *cbarg, - int errorcode, - char const *details) -{ - const tal_t *tmpctx = tal_tmpctx(NULL); - struct sendpay_result *result; - - result = sendpay_result_simple_fail(tmpctx, errorcode, details); - - cb(result, cbarg); - - tal_free(tmpctx); -} -/* Immediately fail during send_payment call. */ -static void -sendpay_succeed_now(void (*cb)(const struct sendpay_result*, void*), - void *cbarg, - const struct preimage *payment_preimage) -{ - const tal_t *tmpctx = tal_tmpctx(NULL); - struct sendpay_result *result; - - result = sendpay_result_success(tmpctx, payment_preimage); - - cb(result, cbarg); - - tal_free(tmpctx); -} - void payment_succeeded(struct lightningd *ld, struct htlc_out *hout, const struct preimage *rval) { @@ -496,17 +465,76 @@ void payment_failed(struct lightningd *ld, const struct htlc_out *hout, tal_free(tmpctx); } -/* Returns false if we called callback directly, true if - * callback is scheduled for later. - * - * This call expects that if it calls the callback, then - * the given context should have been freed. */ -bool send_payment(const tal_t *ctx, - struct lightningd* ld, - const struct sha256 *rhash, - const struct route_hop *route, - void (*cb)(const struct sendpay_result*, void*), +/* Wait for a payment. If cxt is deleted, then cb will + * no longer be called. + * Return false if we called callback already, true if + * callback is scheduled for later. */ +bool wait_payment(const tal_t *cxt, + struct lightningd *ld, + const struct sha256 *payment_hash, + void (*cb)(const struct sendpay_result *, void*), void *cbarg) +{ + const tal_t *tmpctx = tal_tmpctx(cxt); + struct wallet_payment *payment; + struct sendpay_result *result; + char const *details; + bool cb_not_called; + + payment = wallet_payment_by_hash(tmpctx, ld->wallet, payment_hash); + if (!payment) { + details = tal_fmt(tmpctx, + "Never attempted payment for '%s'", + type_to_string(tmpctx, struct sha256, + payment_hash)); + result = sendpay_result_simple_fail(tmpctx, + PAY_NO_SUCH_PAYMENT, + details); + cb(result, cbarg); + cb_not_called = false; + goto end; + } + + switch (payment->status) { + case PAYMENT_PENDING: + add_payment_waiter(cxt, payment_hash, ld, cb, cbarg); + cb_not_called = true; + goto end; + + case PAYMENT_COMPLETE: + result = sendpay_result_success(tmpctx, + payment->payment_preimage); + cb(result, cbarg); + cb_not_called = false; + goto end; + + case PAYMENT_FAILED: + /* FIXME: store the failure and other failure data + * on the DB so that we can do something other than + * unspecified-error. */ + result = sendpay_result_simple_fail(tmpctx, + PAY_UNSPECIFIED_ERROR, + "Payment already failed"); + cb(result, cbarg); + cb_not_called = false; + goto end; + } + + /* Impossible. */ + abort(); + +end: + tal_free(tmpctx); + return cb_not_called; +} + +/* Returns the result if available now, or NULL if the + * sendpay was deferred for later. */ +struct sendpay_result * +send_payment(const tal_t *ctx, + struct lightningd* ld, + const struct sha256 *rhash, + const struct route_hop *route) { const u8 *onion; u8 sessionkey[32]; @@ -523,6 +551,7 @@ bool send_payment(const tal_t *ctx, struct short_channel_id *channels; struct routing_failure *fail; struct channel *channel; + struct sendpay_result *result; /* Expiry for HTLCs is absolute. And add one to give some margin. */ base_expiry = get_block_height(ld->topology) + 1; @@ -553,60 +582,57 @@ bool send_payment(const tal_t *ctx, log_debug(ld->log, "send_payment: found previous"); if (payment->status == PAYMENT_PENDING) { log_add(ld->log, "Payment is still in progress"); - sendpay_fail_now(cb, cbarg, PAY_IN_PROGRESS, - "Payment is still in progress"); - return false; + tal_free(tmpctx); + return sendpay_result_simple_fail(ctx, + PAY_IN_PROGRESS, + "Payment is still in progress"); } if (payment->status == PAYMENT_COMPLETE) { log_add(ld->log, "... succeeded"); /* Must match successful payment parameters. */ if (payment->msatoshi != hop_data[n_hops-1].amt_forward) { - char *msg = tal_fmt(tmpctx, + char *msg = tal_fmt(ctx, "Already succeeded " "with amount %"PRIu64, payment->msatoshi); - sendpay_fail_now(cb, cbarg, - PAY_RHASH_ALREADY_USED, - msg); - return false; + tal_free(tmpctx); + return sendpay_result_simple_fail(ctx, + PAY_RHASH_ALREADY_USED, + msg); } if (!structeq(&payment->destination, &ids[n_hops-1])) { - char *msg = tal_fmt(tmpctx, + char *msg = tal_fmt(ctx, "Already succeeded to %s", type_to_string(tmpctx, struct pubkey, &payment->destination)); - sendpay_fail_now(cb, cbarg, - PAY_RHASH_ALREADY_USED, - msg); - return false; + tal_free(tmpctx); + return sendpay_result_simple_fail(ctx, + PAY_RHASH_ALREADY_USED, + msg); } - sendpay_succeed_now(cb, cbarg, - payment->payment_preimage); - return false; + result = sendpay_result_success(ctx, + payment->payment_preimage); + tal_free(tmpctx); + return result; } wallet_payment_delete(ld->wallet, rhash); log_add(ld->log, "... retrying"); } - /* At this point we know there is no duplicate payment. - * Register it to the lightningd. Use the caller - * context, not our temporary context. */ - new_sendpay_command(ctx, rhash, ld, cb, cbarg); - channel = active_channel_by_id(ld, &ids[0], NULL); if (!channel) { /* Report routing failure to gossipd */ - fail = immediate_routing_failure(tmpctx, ld, + fail = immediate_routing_failure(ctx, ld, WIRE_UNKNOWN_NEXT_PEER, &route[0].channel_id); report_routing_failure(ld->log, ld->gossip, fail); - /* Report routing failure to user */ - sendpay_route_failure(ld, rhash, true, fail, NULL, - "No connection to first " - "peer found"); - return false; + /* Report routing failure to caller */ + tal_free(tmpctx); + return sendpay_result_route_failure(ctx, true, fail, NULL, + "No connection to first " + "peer found"); } randombytes_buf(&sessionkey, sizeof(sessionkey)); @@ -624,15 +650,15 @@ bool send_payment(const tal_t *ctx, rhash, onion, NULL, &hout); if (failcode) { /* Report routing failure to gossipd */ - fail = immediate_routing_failure(tmpctx, ld, + fail = immediate_routing_failure(ctx, ld, failcode, &route[0].channel_id); report_routing_failure(ld->log, ld->gossip, fail); - /* Report routing failure to user */ - sendpay_route_failure(ld, rhash, true, fail, NULL, - "First peer not ready"); - return false; + /* Report routing failure to caller */ + tal_free(tmpctx); + return sendpay_result_route_failure(ctx, true, fail, NULL, + "First peer not ready"); } /* Copy channels used along the route. */ @@ -657,7 +683,7 @@ bool send_payment(const tal_t *ctx, wallet_payment_setup(ld->wallet, payment); tal_free(tmpctx); - return true; + return NULL; } /*----------------------------------------------------------------------------- @@ -672,6 +698,7 @@ json_sendpay_success(struct command *cmd, response = new_json_result(cmd); json_object_start(response, NULL); + json_add_bool(response, "completed", true); json_add_hex(response, "payment_preimage", payment_preimage, sizeof(*payment_preimage)); json_object_end(response); @@ -693,6 +720,8 @@ static void json_sendpay_on_resolve(const struct sendpay_result *r, switch (r->errorcode) { case PAY_IN_PROGRESS: case PAY_RHASH_ALREADY_USED: + case PAY_UNSPECIFIED_ERROR: + case PAY_NO_SUCH_PAYMENT: data = NULL; msg = r->details; break; @@ -755,6 +784,8 @@ static void json_sendpay(struct command *cmd, size_t n_hops; struct sha256 rhash; struct route_hop *route; + struct sendpay_result *r; + struct json_result *response; if (!json_get_params(cmd, buffer, params, "route", &routetok, @@ -833,9 +864,18 @@ static void json_sendpay(struct command *cmd, return; } - if (send_payment(cmd, cmd->ld, &rhash, route, - &json_sendpay_on_resolve, cmd)) - command_still_pending(cmd); + r = send_payment(cmd, cmd->ld, &rhash, route); + if (r) + json_sendpay_on_resolve(r, cmd); + else { + response = new_json_result(cmd); + json_object_start(response, NULL); + json_add_string(response, "message", + "Monitor status with listpayments or waitsendpay"); + json_add_bool(response, "completed", false); + json_object_end(response); + command_success(cmd, response); + } } static const struct json_command sendpay_command = { @@ -845,6 +885,60 @@ static const struct json_command sendpay_command = { }; AUTODATA(json_command, &sendpay_command); +static void waitsendpay_timeout(struct command *cmd) +{ + command_fail_detailed(cmd, PAY_IN_PROGRESS, NULL, + "Timed out while waiting"); +} + +static void json_waitsendpay(struct command *cmd, const char *buffer, + const jsmntok_t *params) +{ + jsmntok_t *rhashtok; + jsmntok_t *timeouttok; + struct sha256 rhash; + unsigned int timeout; + + if (!json_get_params(cmd, buffer, params, + "payment_hash", &rhashtok, + "?timeout", &timeouttok, + NULL)) + return; + + if (!hex_decode(buffer + rhashtok->start, + rhashtok->end - rhashtok->start, + &rhash, sizeof(rhash))) { + command_fail(cmd, "'%.*s' is not a valid sha256 hash", + rhashtok->end - rhashtok->start, + buffer + rhashtok->start); + return; + } + + if (timeouttok && !json_tok_number(buffer, timeouttok, &timeout)) { + command_fail(cmd, "'%.*s' is not a valid number", + timeouttok->end - timeouttok->start, + buffer + timeouttok->start); + return; + } + + if (!wait_payment(cmd, cmd->ld, &rhash, &json_sendpay_on_resolve, cmd)) + return; + + if (timeouttok) + new_reltimer(&cmd->ld->timers, cmd, time_from_sec(timeout), + &waitsendpay_timeout, cmd); + + command_still_pending(cmd); +} + +static const struct json_command waitsendpay_command = { + "waitsendpay", + json_waitsendpay, + "Wait for payment attempt on {payment_hash} to succeed or fail, " + "but only up to {timeout} seconds." +}; +AUTODATA(json_command, &waitsendpay_command); + static void json_listpayments(struct command *cmd, const char *buffer, const jsmntok_t *params) { diff --git a/lightningd/pay.h b/lightningd/pay.h index 152e5c885..38e9786f1 100644 --- a/lightningd/pay.h +++ b/lightningd/pay.h @@ -40,11 +40,27 @@ struct sendpay_result { const char *details; }; -bool send_payment(const tal_t *ctx, +/* Initiate a payment. Return NULL if the payment will be + * scheduled for later, or a result if the result is available + * immediately. If returning an immediate result, the returned + * object is allocated from the given context. Otherwise, the + * return context is ignored. */ +struct sendpay_result *send_payment(const tal_t *ctx, + struct lightningd* ld, + const struct sha256 *rhash, + const struct route_hop *route); +/* Wait for a previous send_payment to complete in definite + * success or failure. If the given context is freed before + * the callback is called, then the callback will no longer + * be called. + * + * Return true if the payment is still pending on return, or + * false if the callback was already invoked before this + * function returned. */ +bool wait_payment(const tal_t *ctx, struct lightningd* ld, - const struct sha256 *rhash, - const struct route_hop *route, - void (*cb)(const struct sendpay_result*, void*), + const struct sha256 *payment_hash, + void (*cb)(const struct sendpay_result *, void *cbarg), void *cbarg); void payment_succeeded(struct lightningd *ld, struct htlc_out *hout, diff --git a/lightningd/payalgo.c b/lightningd/payalgo.c index 83d5ca946..4af6ebc7f 100644 --- a/lightningd/payalgo.c +++ b/lightningd/payalgo.c @@ -234,6 +234,7 @@ static void json_pay_getroute_reply(struct subd *gossip UNUSED, double feepercent; bool fee_too_high; struct json_result *data; + struct sendpay_result *result; fromwire_gossip_getroute_reply(reply, reply, &route); @@ -293,9 +294,18 @@ static void json_pay_getroute_reply(struct subd *gossip UNUSED, ++pay->sendpay_tries; log_route(pay, route); - send_payment(pay->try_parent, - pay->cmd->ld, &pay->payment_hash, route, - &json_pay_sendpay_resolve, pay); + + result = send_payment(pay->try_parent, + pay->cmd->ld, &pay->payment_hash, route); + /* Resolved immediately? */ + if (result) + json_pay_sendpay_resolve(result, pay); + /* Wait for resolution */ + else + wait_payment(pay->try_parent, + pay->cmd->ld, + &pay->payment_hash, + &json_pay_sendpay_resolve, pay); } /* Start a payment attempt. Return true if deferred,