pay: Generalize internal interface of sendpay.

This commit is contained in:
ZmnSCPxj 2018-02-14 00:09:23 +00:00 committed by Christian Decker
parent 700dda7e60
commit 7ee6ccfbd7

View File

@ -19,62 +19,9 @@
#include <lightningd/subd.h>
#include <sodium/randombytes.h>
/* sendpay command */
struct sendpay_command {
struct list_node list;
struct sha256 payment_hash;
struct command *cmd;
};
static void destroy_sendpay_command(struct sendpay_command *pc)
{
list_del(&pc->list);
}
/* Owned by cmd */
static struct sendpay_command *
new_sendpay_command(struct command *cmd,
const struct sha256 *payment_hash,
struct lightningd *ld)
{
struct sendpay_command *pc = tal(cmd, struct sendpay_command);
pc->payment_hash = *payment_hash;
pc->cmd = cmd;
list_add(&ld->sendpay_commands, &pc->list);
tal_add_destructor(pc, destroy_sendpay_command);
return pc;
}
static void
json_sendpay_command_success(struct command *cmd,
const struct preimage *payment_preimage)
{
struct json_result *response;
response = new_json_result(cmd);
json_object_start(response, NULL);
json_add_hex(response, "preimage",
payment_preimage, sizeof(*payment_preimage));
json_object_end(response);
command_success(cmd, response);
}
static void json_pay_success(struct lightningd *ld,
const struct sha256 *payment_hash,
const struct preimage *payment_preimage)
{
struct sendpay_command *pc, *next;
list_for_each_safe(&ld->sendpay_commands, pc, next, list) {
if (!structeq(payment_hash, &pc->payment_hash))
continue;
/* Deletes itself. */
json_sendpay_command_success(pc->cmd, payment_preimage);
}
}
/*-----------------------------------------------------------------------------
Internal sendpay interface
-----------------------------------------------------------------------------*/
struct routing_failure {
unsigned int erring_index;
@ -84,76 +31,162 @@ struct routing_failure {
u8 *channel_update;
};
static void
json_sendpay_command_routing_failed(struct command *cmd,
bool retry_plausible,
const struct routing_failure *fail,
const u8 *onionreply,
const char *details)
/* Result of sendpay */
struct sendpay_result {
/* Did the payment succeed? */
bool succeeded;
/* Preimage. Only loaded if payment succeeded. */
struct preimage preimage;
/* Error code, one of the PAY_* macro in jsonrpc_errors.h.
* Only loaded if payment failed. */
int errorcode;
/* Unparseable onion reply. Only loaded if payment failed,
* and errorcode == PAY_UNPARSEABLE_ONION. */
const u8* onionreply;
/* Routing failure object. Only loaded if payment failed,
* and errorcode == PAY_DESTINATION_PERM_FAIL or
* errorcode == PAY_TRY_OTHER_ROUTE */
struct routing_failure* routing_failure;
/* Error message. Only loaded if payment failed. */
const char *details;
};
/* sendpay command */
struct sendpay_command {
struct list_node list;
struct sha256 payment_hash;
void (*cb)(const struct sendpay_result *, void*);
void *cbarg;
};
static void destroy_sendpay_command(struct sendpay_command *pc)
{
int code =
(!fail) ? PAY_UNPARSEABLE_ONION :
(!retry_plausible) ? PAY_DESTINATION_PERM_FAIL :
/*otherwise*/ PAY_TRY_OTHER_ROUTE ;
struct json_result *data = new_json_result(cmd);
enum onion_type failure_code;
/* Prepare data. */
json_object_start(data, NULL);
if (fail) {
failure_code = fail->failcode;
json_add_num(data, "erring_index", fail->erring_index);
json_add_num(data, "failcode", (unsigned) fail->failcode);
json_add_hex(data, "erring_node",
&fail->erring_node, sizeof(fail->erring_node));
json_add_short_channel_id(data, "erring_channel",
&fail->erring_channel);
if (fail->channel_update)
json_add_hex(data, "channel_update",
fail->channel_update,
tal_len(fail->channel_update));
} else {
assert(onionreply);
failure_code = WIRE_PERMANENT_NODE_FAILURE;
json_add_hex(data, "onionreply",
onionreply, tal_len(onionreply));
}
json_object_end(data);
/* Deletes cmd. */
command_fail_detailed(cmd, code, data, "failed: %s (%s)",
onion_type_name(failure_code),
details);
list_del(&pc->list);
}
static void json_pay_failed(struct lightningd *ld,
const struct sha256 *payment_hash,
bool retry_plausible,
const struct routing_failure *fail,
const u8 *onionreply,
const char *details)
/* 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)
{
struct sendpay_command *pc, *next;
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);
return pc;
}
/* Caller responsible for freeing ctx. */
static void sendpay_resolve(const tal_t *ctx,
struct lightningd *ld,
const struct sha256 *payment_hash,
const struct sendpay_result *result)
{
struct sendpay_command *pc;
struct sendpay_command *next;
list_for_each_safe(&ld->sendpay_commands, pc, next, list) {
if (!structeq(payment_hash, &pc->payment_hash))
continue;
/* Deletes cmd. */
json_sendpay_command_routing_failed(pc->cmd,
retry_plausible,
fail,
onionreply,
details);
/* Delete later (in our own caller) if callback did
* not delete. */
tal_steal(ctx, pc);
pc->cb(result, pc->cbarg);
}
}
static void sendpay_success(struct lightningd *ld,
const struct sha256 *payment_hash,
const struct preimage *payment_preimage)
{
const tal_t *tmpctx = tal_tmpctx(ld);
struct sendpay_result *result;
result = tal(tmpctx, struct sendpay_result);
result->succeeded = true;
result->preimage = *payment_preimage;
sendpay_resolve(tmpctx, ld, payment_hash, result);
tal_free(tmpctx);
}
static void sendpay_route_failure(struct lightningd *ld,
const struct sha256 *payment_hash,
bool retry_plausible,
struct routing_failure *fail,
const u8 *onionreply,
const char *details)
{
const tal_t *tmpctx = tal_tmpctx(ld);
struct sendpay_result *result;
result = tal(tmpctx, struct sendpay_result);
result->succeeded = false;
result->errorcode =
(!fail) ? PAY_UNPARSEABLE_ONION :
(!retry_plausible) ? PAY_DESTINATION_PERM_FAIL :
/*otherwise*/ PAY_TRY_OTHER_ROUTE ;
result->onionreply = onionreply;
result->routing_failure = fail;
result->details = details;
sendpay_resolve(tmpctx, ld, payment_hash, result);
tal_free(tmpctx);
}
/* 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 = tal(tmpctx, struct sendpay_result);
result->succeeded = false;
result->errorcode = errorcode;
result->details = 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 = tal(tmpctx, struct sendpay_result);
result->succeeded = true;
result->preimage = *payment_preimage;
cb(result, cbarg);
tal_free(tmpctx);
}
void payment_succeeded(struct lightningd *ld, struct htlc_out *hout,
const struct preimage *rval)
{
wallet_payment_set_status(ld->wallet, &hout->payment_hash,
PAYMENT_COMPLETE, rval);
json_pay_success(ld, &hout->payment_hash, rval);
sendpay_success(ld, &hout->payment_hash, rval);
}
/* Return NULL if the wrapped onion error message has no
@ -445,17 +478,24 @@ void payment_failed(struct lightningd *ld, const struct htlc_out *hout,
* and payment again. */
(void) retry_plausible;
/* Report to RPC. */
json_pay_failed(ld, &hout->payment_hash,
retry_plausible, fail, hout->failuremsg,
failmsg);
/* Report to client. */
sendpay_route_failure(ld, &hout->payment_hash,
retry_plausible, fail, hout->failuremsg,
failmsg);
tal_free(tmpctx);
}
/* Returns true if it's still pending. */
static bool send_payment(struct command *cmd,
/* 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. */
static bool send_payment(const tal_t *ctx,
struct lightningd* ld,
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;
u8 sessionkey[32];
@ -463,8 +503,7 @@ static bool send_payment(struct command *cmd,
struct onionpacket *packet;
struct secret *path_secrets;
enum onion_type failcode;
/* Freed automatically on cmd completion: only manually at end. */
const tal_t *tmpctx = tal_tmpctx(cmd);
const tal_t *tmpctx = tal_tmpctx(ctx);
size_t i, n_hops = tal_count(route);
struct hop_data *hop_data = tal_arr(tmpctx, struct hop_data, n_hops);
struct pubkey *ids = tal_arr(tmpctx, struct pubkey, n_hops);
@ -475,7 +514,7 @@ static bool send_payment(struct command *cmd,
struct channel *channel;
/* Expiry for HTLCs is absolute. And add one to give some margin. */
base_expiry = get_block_height(cmd->ld->topology) + 1;
base_expiry = get_block_height(ld->topology) + 1;
/* Extract IDs for each hop: create_onionpacket wants array. */
for (i = 0; i < n_hops; i++)
@ -497,68 +536,76 @@ static bool send_payment(struct command *cmd,
hop_data[i].amt_forward = route[i].amount;
/* Now, do we already have a payment? */
payment = wallet_payment_by_hash(tmpctx, cmd->ld->wallet, rhash);
payment = wallet_payment_by_hash(tmpctx, ld->wallet, rhash);
if (payment) {
/* FIXME: We should really do something smarter here! */
log_debug(cmd->ld->log, "json_sendpay: found previous");
log_debug(ld->log, "send_payment: found previous");
if (payment->status == PAYMENT_PENDING) {
log_add(cmd->ld->log, "Payment is still in progress");
command_fail_detailed(cmd, PAY_IN_PROGRESS, NULL,
"Payment is still in progress");
log_add(ld->log, "Payment is still in progress");
sendpay_fail_now(cb, cbarg, PAY_IN_PROGRESS,
"Payment is still in progress");
return false;
}
if (payment->status == PAYMENT_COMPLETE) {
log_add(cmd->ld->log, "... succeeded");
log_add(ld->log, "... succeeded");
/* Must match successful payment parameters. */
if (payment->msatoshi != hop_data[n_hops-1].amt_forward) {
command_fail_detailed(cmd,
PAY_RHASH_ALREADY_USED,
NULL,
"Already succeeded "
"with amount %"PRIu64,
payment->msatoshi);
char *msg = tal_fmt(tmpctx,
"Already succeeded "
"with amount %"PRIu64,
payment->msatoshi);
sendpay_fail_now(cb, cbarg,
PAY_RHASH_ALREADY_USED,
msg);
return false;
}
if (!structeq(&payment->destination, &ids[n_hops-1])) {
command_fail_detailed(cmd,
PAY_RHASH_ALREADY_USED,
NULL,
"Already succeeded to %s",
type_to_string(cmd, struct pubkey,
&payment->destination));
char *msg = tal_fmt(tmpctx,
"Already succeeded to %s",
type_to_string(tmpctx,
struct pubkey,
&payment->destination));
sendpay_fail_now(cb, cbarg,
PAY_RHASH_ALREADY_USED,
msg);
return false;
}
json_sendpay_command_success(cmd,
payment->payment_preimage);
sendpay_succeed_now(cb, cbarg,
payment->payment_preimage);
return false;
}
wallet_payment_delete(cmd->ld->wallet, rhash);
log_add(cmd->ld->log, "... retrying");
wallet_payment_delete(ld->wallet, rhash);
log_add(ld->log, "... retrying");
}
channel = active_channel_by_id(cmd->ld, &ids[0]);
/* 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]);
if (!channel) {
/* Report routing failure to gossipd */
fail = immediate_routing_failure(cmd, cmd->ld,
fail = immediate_routing_failure(tmpctx, ld,
WIRE_UNKNOWN_NEXT_PEER,
&route[0].channel_id);
report_routing_failure(cmd->ld->log, cmd->ld->gossip, fail);
report_routing_failure(ld->log, ld->gossip, fail);
/* Report routing failure to user */
json_sendpay_command_routing_failed(cmd, true, fail, NULL,
"No connection to first "
"peer found");
sendpay_route_failure(ld, rhash, true, fail, NULL,
"No connection to first "
"peer found");
return false;
}
randombytes_buf(&sessionkey, sizeof(sessionkey));
/* Onion will carry us from first peer onwards. */
packet = create_onionpacket(cmd, ids, hop_data, sessionkey, rhash->u.u8,
packet = create_onionpacket(tmpctx, ids, hop_data, sessionkey, rhash->u.u8,
sizeof(struct sha256), &path_secrets);
onion = serialize_onionpacket(cmd, packet);
onion = serialize_onionpacket(tmpctx, packet);
log_info(cmd->ld->log, "Sending %u over %zu hops to deliver %u",
log_info(ld->log, "Sending %u over %zu hops to deliver %u",
route[0].amount, n_hops, route[n_hops-1].amount);
failcode = send_htlc_out(channel, route[0].amount,
@ -566,14 +613,14 @@ static bool send_payment(struct command *cmd,
rhash, onion, NULL, &hout);
if (failcode) {
/* Report routing failure to gossipd */
fail = immediate_routing_failure(cmd, cmd->ld,
fail = immediate_routing_failure(tmpctx, ld,
failcode,
&route[0].channel_id);
report_routing_failure(cmd->ld->log, cmd->ld->gossip, fail);
report_routing_failure(ld->log, ld->gossip, fail);
/* Report routing failure to user */
json_sendpay_command_routing_failed(cmd, true, fail, NULL,
"First peer not ready");
sendpay_route_failure(ld, rhash, true, fail, NULL,
"First peer not ready");
return false;
}
@ -596,13 +643,96 @@ static bool send_payment(struct command *cmd,
payment->route_channels = tal_steal(payment, channels);
/* We write this into db when HTLC is actually sent. */
wallet_payment_setup(cmd->ld->wallet, payment);
wallet_payment_setup(ld->wallet, payment);
new_sendpay_command(cmd, rhash, cmd->ld);
tal_free(tmpctx);
return true;
}
/*-----------------------------------------------------------------------------
JSON-RPC sendpay interface
-----------------------------------------------------------------------------*/
static void
json_sendpay_success(struct command *cmd,
const struct preimage *payment_preimage)
{
struct json_result *response;
response = new_json_result(cmd);
json_object_start(response, NULL);
json_add_hex(response, "preimage",
payment_preimage, sizeof(*payment_preimage));
json_object_end(response);
command_success(cmd, response);
}
static void json_sendpay_on_resolve(const struct sendpay_result *r,
void *vcmd)
{
struct command *cmd = (struct command*) vcmd;
struct json_result *data;
const char *msg;
struct routing_failure *fail;
if (r->succeeded)
json_sendpay_success(cmd, &r->preimage);
else {
switch (r->errorcode) {
case PAY_IN_PROGRESS:
case PAY_RHASH_ALREADY_USED:
data = NULL;
msg = r->details;
break;
case PAY_UNPARSEABLE_ONION:
data = new_json_result(cmd);
json_object_start(data, NULL);
json_add_hex(data, "onionreply",
r->onionreply, tal_len(r->onionreply));
json_object_end(data);
msg = tal_fmt(cmd,
"failed: WIRE_PERMANENT_NODE_FAILURE "
"(%s)",
r->details);
break;
case PAY_DESTINATION_PERM_FAIL:
case PAY_TRY_OTHER_ROUTE:
fail = r->routing_failure;
data = new_json_result(cmd);
json_object_start(data, NULL);
json_add_num(data, "erring_index",
fail->erring_index);
json_add_num(data, "failcode",
(unsigned) fail->failcode);
json_add_hex(data, "erring_node",
&fail->erring_node,
sizeof(fail->erring_node));
json_add_short_channel_id(data, "erring_channel",
&fail->erring_channel);
if (fail->channel_update)
json_add_hex(data, "channel_update",
fail->channel_update,
tal_len(fail->channel_update));
json_object_end(data);
msg = tal_fmt(cmd,
"failed: %s (%s)",
onion_type_name(fail->failcode),
r->details);
break;
}
command_fail_detailed(cmd, r->errorcode, data, "%s", msg);
}
}
static void json_sendpay(struct command *cmd,
const char *buffer, const jsmntok_t *params)
{
@ -689,7 +819,8 @@ static void json_sendpay(struct command *cmd,
return;
}
if (send_payment(cmd, &rhash, route))
if (send_payment(cmd, cmd->ld, &rhash, route,
&json_sendpay_on_resolve, cmd))
command_still_pending(cmd);
}
@ -755,7 +886,8 @@ static void json_pay_getroute_reply(struct subd *gossip,
return;
}
send_payment(pay->cmd, &pay->payment_hash, route);
send_payment(pay->cmd, pay->cmd->ld, &pay->payment_hash, route,
&json_sendpay_on_resolve, pay->cmd);
}
static void json_pay(struct command *cmd,