pay: Have sendpay wait for payment to be saved.

The payment should be stored in a "timely" manner, i.e.
within 10ms.
This commit is contained in:
ZmnSCPxj 2018-03-10 07:44:28 +00:00 committed by Rusty Russell
parent a7a18b96cf
commit a0c2686ebd
5 changed files with 147 additions and 69 deletions

View File

@ -68,6 +68,7 @@ static struct lightningd *new_lightningd(const tal_t *ctx)
ld->rgb = NULL; ld->rgb = NULL;
list_head_init(&ld->connects); list_head_init(&ld->connects);
list_head_init(&ld->waitsendpay_commands); list_head_init(&ld->waitsendpay_commands);
list_head_init(&ld->sendpay_commands);
ld->wireaddrs = tal_arr(ld, struct wireaddr, 0); ld->wireaddrs = tal_arr(ld, struct wireaddr, 0);
ld->portnum = DEFAULT_PORT; ld->portnum = DEFAULT_PORT;
timers_init(&ld->timers, time_mono()); timers_init(&ld->timers, time_mono());

View File

@ -135,6 +135,8 @@ struct lightningd {
/* Outstanding waitsendpay commands. */ /* Outstanding waitsendpay commands. */
struct list_head waitsendpay_commands; struct list_head waitsendpay_commands;
/* Outstanding sendpay commands. */
struct list_head sendpay_commands;
/* Maintained by invoices.c */ /* Maintained by invoices.c */
struct invoices *invoices; struct invoices *invoices;

View File

@ -36,10 +36,10 @@ static void destroy_sendpay_command(struct sendpay_command *pc)
list_del(&pc->list); list_del(&pc->list);
} }
/* Owned by cxt; if cxt is deleted, then cb will /* Owned by cxt, if cxt is deleted, then cb will
* no longer be called. */ * no longer be called. */
static void static void
add_payment_waiter(const tal_t *cxt, add_sendpay_waiter(const tal_t *cxt,
const struct sha256 *payment_hash, const struct sha256 *payment_hash,
struct lightningd *ld, struct lightningd *ld,
void (*cb)(const struct sendpay_result *, void*), void (*cb)(const struct sendpay_result *, void*),
@ -47,6 +47,24 @@ add_payment_waiter(const tal_t *cxt,
{ {
struct sendpay_command *pc = tal(cxt, struct sendpay_command); struct sendpay_command *pc = tal(cxt, struct sendpay_command);
pc->payment_hash = *payment_hash;
pc->cb = cb;
pc->cbarg = cbarg;
list_add(&ld->sendpay_commands, &pc->list);
tal_add_destructor(pc, destroy_sendpay_command);
}
/* Owned by cxt; if cxt is deleted, then cb will
* no longer be called. */
static void
add_waitsendpay_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);
pc->payment_hash = *payment_hash; pc->payment_hash = *payment_hash;
pc->cb = cb; pc->cb = cb;
pc->cbarg = cbarg; pc->cbarg = cbarg;
@ -368,7 +386,28 @@ static void report_routing_failure(struct log *log,
void payment_store(struct lightningd *ld, void payment_store(struct lightningd *ld,
const struct sha256 *payment_hash) const struct sha256 *payment_hash)
{ {
const tal_t *tmpctx = tal_tmpctx(ld);
struct sendpay_command *pc;
struct sendpay_command *next;
struct sendpay_result *result;
wallet_payment_store(ld->wallet, payment_hash); wallet_payment_store(ld->wallet, payment_hash);
/* Invent a sendpay result with PAY_IN_PROGRESS. */
result = sendpay_result_simple_fail(tmpctx, PAY_IN_PROGRESS,
"Payment is still in progress");
/* Trigger any sendpay commands waiting for the store to occur. */
list_for_each_safe(&ld->sendpay_commands, pc, next, list) {
if (!structeq(payment_hash, &pc->payment_hash))
continue;
/* Delete later if callback did not delete. */
tal_steal(tmpctx, pc);
pc->cb(result, pc->cbarg);
}
tal_free(tmpctx);
} }
void payment_failed(struct lightningd *ld, const struct htlc_out *hout, void payment_failed(struct lightningd *ld, const struct htlc_out *hout,
@ -490,7 +529,7 @@ bool wait_payment(const tal_t *cxt,
void (*cb)(const struct sendpay_result *, void*), void (*cb)(const struct sendpay_result *, void*),
void *cbarg) void *cbarg)
{ {
const tal_t *tmpctx = tal_tmpctx(cxt); const tal_t *tmpctx = tal_tmpctx(NULL);
struct wallet_payment *payment; struct wallet_payment *payment;
struct sendpay_result *result; struct sendpay_result *result;
char const *details; char const *details;
@ -520,7 +559,7 @@ bool wait_payment(const tal_t *cxt,
switch (payment->status) { switch (payment->status) {
case PAYMENT_PENDING: case PAYMENT_PENDING:
add_payment_waiter(cxt, payment_hash, ld, cb, cbarg); add_waitsendpay_waiter(cxt, payment_hash, ld, cb, cbarg);
cb_not_called = true; cb_not_called = true;
goto end; goto end;
@ -575,13 +614,14 @@ end:
return cb_not_called; return cb_not_called;
} }
/* Returns the result if available now, or NULL if the /* Returns false if cb was called, true if cb not yet called. */
* sendpay was deferred for later. */ bool
struct sendpay_result *
send_payment(const tal_t *ctx, send_payment(const tal_t *ctx,
struct lightningd* ld, struct lightningd* ld,
const struct sha256 *rhash, const struct sha256 *rhash,
const struct route_hop *route) const struct route_hop *route,
void (*cb)(const struct sendpay_result *, void*),
void *cbarg)
{ {
const u8 *onion; const u8 *onion;
u8 sessionkey[32]; u8 sessionkey[32];
@ -629,39 +669,42 @@ send_payment(const tal_t *ctx,
log_debug(ld->log, "send_payment: found previous"); log_debug(ld->log, "send_payment: found previous");
if (payment->status == PAYMENT_PENDING) { if (payment->status == PAYMENT_PENDING) {
log_add(ld->log, "Payment is still in progress"); log_add(ld->log, "Payment is still in progress");
tal_free(tmpctx); result = sendpay_result_simple_fail(tmpctx,
return sendpay_result_simple_fail(ctx, PAY_IN_PROGRESS,
PAY_IN_PROGRESS, "Payment is still in progress");
"Payment is still in progress"); cb(result, cbarg);
return false;
} }
if (payment->status == PAYMENT_COMPLETE) { if (payment->status == PAYMENT_COMPLETE) {
log_add(ld->log, "... succeeded"); log_add(ld->log, "... succeeded");
/* Must match successful payment parameters. */ /* Must match successful payment parameters. */
if (payment->msatoshi != hop_data[n_hops-1].amt_forward) { if (payment->msatoshi != hop_data[n_hops-1].amt_forward) {
char *msg = tal_fmt(ctx, char *msg = tal_fmt(tmpctx,
"Already succeeded " "Already succeeded "
"with amount %"PRIu64, "with amount %"PRIu64,
payment->msatoshi); payment->msatoshi);
tal_free(tmpctx); result = sendpay_result_simple_fail(tmpctx,
return sendpay_result_simple_fail(ctx, PAY_RHASH_ALREADY_USED,
PAY_RHASH_ALREADY_USED, msg);
msg); cb(result, cbarg);
return false;
} }
if (!structeq(&payment->destination, &ids[n_hops-1])) { if (!structeq(&payment->destination, &ids[n_hops-1])) {
char *msg = tal_fmt(ctx, char *msg = tal_fmt(tmpctx,
"Already succeeded to %s", "Already succeeded to %s",
type_to_string(tmpctx, type_to_string(tmpctx,
struct pubkey, struct pubkey,
&payment->destination)); &payment->destination));
tal_free(tmpctx); result = sendpay_result_simple_fail(tmpctx,
return sendpay_result_simple_fail(ctx, PAY_RHASH_ALREADY_USED,
PAY_RHASH_ALREADY_USED, msg);
msg); cb(result, cbarg);
return false;
} }
result = sendpay_result_success(ctx, result = sendpay_result_success(tmpctx,
payment->payment_preimage); payment->payment_preimage);
tal_free(tmpctx); cb(result, cbarg);
return result; return false;
} }
wallet_payment_delete(ld->wallet, rhash); wallet_payment_delete(ld->wallet, rhash);
log_add(ld->log, "... retrying"); log_add(ld->log, "... retrying");
@ -676,10 +719,11 @@ send_payment(const tal_t *ctx,
report_routing_failure(ld->log, ld->gossip, fail); report_routing_failure(ld->log, ld->gossip, fail);
/* Report routing failure to caller */ /* Report routing failure to caller */
tal_free(tmpctx); result = sendpay_result_route_failure(tmpctx, true, fail, NULL,
return sendpay_result_route_failure(ctx, true, fail, NULL, "No connection to first "
"No connection to first " "peer found");
"peer found"); cb(result, cbarg);
return false;
} }
randombytes_buf(&sessionkey, sizeof(sessionkey)); randombytes_buf(&sessionkey, sizeof(sessionkey));
@ -703,9 +747,10 @@ send_payment(const tal_t *ctx,
report_routing_failure(ld->log, ld->gossip, fail); report_routing_failure(ld->log, ld->gossip, fail);
/* Report routing failure to caller */ /* Report routing failure to caller */
tal_free(tmpctx); result = sendpay_result_route_failure(tmpctx, true, fail, NULL,
return sendpay_result_route_failure(ctx, true, fail, NULL, "First peer not ready");
"First peer not ready"); cb(result, cbarg);
return false;
} }
/* Copy channels used along the route. */ /* Copy channels used along the route. */
@ -729,8 +774,10 @@ send_payment(const tal_t *ctx,
/* We write this into db when HTLC is actually sent. */ /* We write this into db when HTLC is actually sent. */
wallet_payment_setup(ld->wallet, payment); wallet_payment_setup(ld->wallet, payment);
add_sendpay_waiter(ctx, rhash, ld, cb, cbarg);
tal_free(tmpctx); tal_free(tmpctx);
return NULL; return true;
} }
/*----------------------------------------------------------------------------- /*-----------------------------------------------------------------------------
@ -827,7 +874,19 @@ static void json_sendpay_on_resolve(const struct sendpay_result* r,
void *vcmd) void *vcmd)
{ {
struct command *cmd = (struct command*) vcmd; struct command *cmd = (struct command*) vcmd;
json_waitsendpay_on_resolve(r, cmd); struct json_result *response;
if (!r->succeeded && r->errorcode == PAY_IN_PROGRESS) {
/* This is normal for sendpay. Succeed. */
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);
} else
json_waitsendpay_on_resolve(r, cmd);
} }
static void json_sendpay(struct command *cmd, static void json_sendpay(struct command *cmd,
@ -838,8 +897,6 @@ static void json_sendpay(struct command *cmd,
size_t n_hops; size_t n_hops;
struct sha256 rhash; struct sha256 rhash;
struct route_hop *route; struct route_hop *route;
struct sendpay_result *r;
struct json_result *response;
if (!json_get_params(cmd, buffer, params, if (!json_get_params(cmd, buffer, params,
"route", &routetok, "route", &routetok,
@ -918,18 +975,9 @@ static void json_sendpay(struct command *cmd,
return; return;
} }
r = send_payment(cmd, cmd->ld, &rhash, route); if (send_payment(cmd, cmd->ld, &rhash, route,
if (r) &json_sendpay_on_resolve, cmd))
json_sendpay_on_resolve(r, cmd); command_still_pending(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 = { static const struct json_command sendpay_command = {

View File

@ -40,15 +40,25 @@ struct sendpay_result {
const char *details; const char *details;
}; };
/* Initiate a payment. Return NULL if the payment will be /* Initiate a payment. Return true if the callback will be
* scheduled for later, or a result if the result is available * scheduled for later, or false if the callback has already
* immediately. If returning an immediate result, the returned * been called. If the given context is freed before the
* object is allocated from the given context. Otherwise, the * callback is called, then the callback will no longer be
* return context is ignored. */ * called.
struct sendpay_result *send_payment(const tal_t *ctx, *
struct lightningd* ld, * This will call the callback "soon" in 10ms or less.
const struct sha256 *rhash, *
const struct route_hop *route); * Typically the callback will be called with a failed
* sendpay_result indicating an error code of PAY_IN_PROGRESS.
* It will only call the callback with successful sendpay_result
* if the payment has already completed with the same amount
* and destination before. */
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*),
void *cbarg);
/* Wait for a previous send_payment to complete in definite /* Wait for a previous send_payment to complete in definite
* success or failure. If the given context is freed before * success or failure. If the given context is freed before
* the callback is called, then the callback will no longer * the callback is called, then the callback will no longer

View File

@ -189,6 +189,12 @@ static void json_pay_sendpay_resolve(const struct sendpay_result *r,
why = should_delay_retry(pay->try_parent, r); why = should_delay_retry(pay->try_parent, r);
if (why) { if (why) {
/* We have some reason to delay retrying. */
/* Clear previous try memory. */
pay->try_parent = tal_free(pay->try_parent);
pay->try_parent = tal(pay, char);
log_info(pay->cmd->ld->log, log_info(pay->cmd->ld->log,
"pay(%p): Delay before retry: %s", pay, why); "pay(%p): Delay before retry: %s", pay, why);
/* Delay for 3 seconds if needed. FIXME: random /* Delay for 3 seconds if needed. FIXME: random
@ -224,6 +230,26 @@ static void log_route(struct pay *pay, struct route_hop *route)
tal_free(tmpctx); tal_free(tmpctx);
} }
static void json_pay_sendpay_resume(const struct sendpay_result *r,
void *vpay)
{
struct pay *pay = (struct pay *) vpay;
bool completed = r->succeeded || r->errorcode != PAY_IN_PROGRESS;
if (completed)
/* Already completed. */
json_pay_sendpay_resolve(r, pay);
else {
/* Clear previous try memory. */
pay->try_parent = tal_free(pay->try_parent);
pay->try_parent = tal(pay, char);
/* Not yet complete? Wait for it. */
wait_payment(pay->try_parent, pay->cmd->ld, &pay->payment_hash,
json_pay_sendpay_resolve, pay);
}
}
static void json_pay_getroute_reply(struct subd *gossip UNUSED, static void json_pay_getroute_reply(struct subd *gossip UNUSED,
const u8 *reply, const int *fds UNUSED, const u8 *reply, const int *fds UNUSED,
struct pay *pay) struct pay *pay)
@ -234,7 +260,6 @@ static void json_pay_getroute_reply(struct subd *gossip UNUSED,
double feepercent; double feepercent;
bool fee_too_high; bool fee_too_high;
struct json_result *data; struct json_result *data;
struct sendpay_result *result;
fromwire_gossip_getroute_reply(reply, reply, &route); fromwire_gossip_getroute_reply(reply, reply, &route);
@ -295,17 +320,9 @@ static void json_pay_getroute_reply(struct subd *gossip UNUSED,
log_route(pay, route); log_route(pay, route);
result = send_payment(pay->try_parent, send_payment(pay->try_parent,
pay->cmd->ld, &pay->payment_hash, route); pay->cmd->ld, &pay->payment_hash, route,
/* Resolved immediately? */ &json_pay_sendpay_resume, pay);
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, /* Start a payment attempt. Return true if deferred,