diff --git a/lightningd/Makefile b/lightningd/Makefile index 42846bce7898..4280a643411d 100644 --- a/lightningd/Makefile +++ b/lightningd/Makefile @@ -63,6 +63,7 @@ LIGHTNINGD_SRC := \ lightningd/opt_time.c \ lightningd/options.c \ lightningd/pay.c \ + lightningd/payalgo.c \ lightningd/peer_control.c \ lightningd/peer_htlcs.c \ lightningd/subd.c \ diff --git a/lightningd/lightningd.c b/lightningd/lightningd.c index 2b3ee7a9e452..4c094f5696f5 100644 --- a/lightningd/lightningd.c +++ b/lightningd/lightningd.c @@ -62,7 +62,7 @@ static struct lightningd *new_lightningd(const tal_t *ctx, ld->alias = NULL; ld->rgb = NULL; list_head_init(&ld->connects); - list_head_init(&ld->pay_commands); + list_head_init(&ld->sendpay_commands); ld->wireaddrs = tal_arr(ld, struct wireaddr, 0); ld->portnum = DEFAULT_PORT; timers_init(&ld->timers, time_mono()); diff --git a/lightningd/lightningd.h b/lightningd/lightningd.h index e5cce9cb5186..53d29d33c7e6 100644 --- a/lightningd/lightningd.h +++ b/lightningd/lightningd.h @@ -129,8 +129,8 @@ struct lightningd { struct wallet *wallet; - /* Outstanding sendpay/pay commands. */ - struct list_head pay_commands; + /* Outstanding sendpay commands. */ + struct list_head sendpay_commands; /* Maintained by invoices.c */ struct invoices *invoices; diff --git a/lightningd/pay.c b/lightningd/pay.c index 06e1bbb8a9f8..30a8ff080202 100644 --- a/lightningd/pay.c +++ b/lightningd/pay.c @@ -3,11 +3,8 @@ #include #include #include -#include #include #include -#include -#include #include #include #include @@ -19,131 +16,138 @@ #include #include -/* pay/sendpay command */ -struct pay_command { +/*----------------------------------------------------------------------------- +Internal sendpay interface +-----------------------------------------------------------------------------*/ + +/* sendpay command */ +struct sendpay_command { struct list_node list; struct sha256 payment_hash; - struct command *cmd; + + void (*cb)(const struct sendpay_result *, void*); + void *cbarg; }; -static void destroy_pay_command(struct pay_command *pc) +static void destroy_sendpay_command(struct sendpay_command *pc) { list_del(&pc->list); } -/* Owned by cmd */ -static struct pay_command *new_pay_command(struct command *cmd, - const struct sha256 *payment_hash, - struct lightningd *ld) +/* 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 pay_command *pc = tal(cmd, struct pay_command); + struct sendpay_command *pc = tal(cxt, struct sendpay_command); pc->payment_hash = *payment_hash; - pc->cmd = cmd; - list_add(&ld->pay_commands, &pc->list); - tal_add_destructor(pc, destroy_pay_command); + pc->cb = cb; + pc->cbarg = cbarg; + list_add(&ld->sendpay_commands, &pc->list); + tal_add_destructor(pc, destroy_sendpay_command); return pc; } -static void json_pay_command_success(struct command *cmd, - const struct preimage *payment_preimage) +/* 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 json_result *response; + 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; - 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); + /* Delete later (in our own caller) if callback did + * not delete. */ + tal_steal(ctx, pc); + pc->cb(result, pc->cbarg); + } } -static void json_pay_success(struct lightningd *ld, - const struct sha256 *payment_hash, - const struct preimage *payment_preimage) +static void sendpay_success(struct lightningd *ld, + const struct sha256 *payment_hash, + const struct preimage *payment_preimage) { - struct pay_command *pc, *next; + const tal_t *tmpctx = tal_tmpctx(ld); + struct sendpay_result *result; - list_for_each_safe(&ld->pay_commands, pc, next, list) { - if (!structeq(payment_hash, &pc->payment_hash)) - continue; + result = tal(tmpctx, struct sendpay_result); + result->succeeded = true; + result->preimage = *payment_preimage; - /* Deletes itself. */ - json_pay_command_success(pc->cmd, payment_preimage); - } -} + sendpay_resolve(tmpctx, ld, payment_hash, result); -struct routing_failure { - unsigned int erring_index; - enum onion_type failcode; - struct pubkey erring_node; - struct short_channel_id erring_channel; - u8 *channel_update; -}; + tal_free(tmpctx); +} -static void -json_pay_command_routing_failed(struct command *cmd, - bool retry_plausible, - const struct routing_failure *fail, - const u8 *onionreply, - const char *details) +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) { - int code = + 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 ; - 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); + result->onionreply = onionreply; + result->routing_failure = fail; + result->details = details; + + sendpay_resolve(tmpctx, ld, payment_hash, result); - /* Deletes cmd. */ - command_fail_detailed(cmd, code, data, "failed: %s (%s)", - onion_type_name(failure_code), - details); + tal_free(tmpctx); } -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) +/* 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) { - struct pay_command *pc, *next; + const tal_t *tmpctx = tal_tmpctx(NULL); + struct sendpay_result *result; - list_for_each_safe(&ld->pay_commands, pc, next, list) { - if (!structeq(payment_hash, &pc->payment_hash)) - continue; + result = tal(tmpctx, struct sendpay_result); + result->succeeded = false; + result->errorcode = errorcode; + result->details = details; - /* Deletes cmd. */ - json_pay_command_routing_failed(pc->cmd, - retry_plausible, - fail, - onionreply, - 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, @@ -151,7 +155,7 @@ void payment_succeeded(struct lightningd *ld, struct htlc_out *hout, { 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 @@ -438,22 +442,24 @@ void payment_failed(struct lightningd *ld, const struct htlc_out *hout, if (report_to_gossipd) report_routing_failure(ld->log, ld->gossip, fail); - /* FIXME(ZmnSCPxj): if retrying is plausible, and we are - * using pay command rather than sendpay, retry routing - * 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, - const struct sha256 *rhash, - const struct route_hop *route) +/* 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*), + void *cbarg) { const u8 *onion; u8 sessionkey[32]; @@ -461,8 +467,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); @@ -473,7 +478,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++) @@ -495,68 +500,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_pay_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_pay_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, @@ -564,14 +577,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_pay_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; } @@ -594,13 +607,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_pay_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) { @@ -687,7 +783,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); } @@ -698,172 +795,6 @@ static const struct json_command sendpay_command = { }; AUTODATA(json_command, &sendpay_command); -struct pay { - struct sha256 payment_hash; - struct command *cmd; - u64 msatoshi; - double maxfeepercent; -}; - -static void json_pay_getroute_reply(struct subd *gossip, - const u8 *reply, const int *fds, - struct pay *pay) -{ - struct route_hop *route; - u64 msatoshi_sent; - u64 fee; - double feepercent; - struct json_result *data; - - fromwire_gossip_getroute_reply(reply, reply, NULL, &route); - - if (tal_count(route) == 0) { - command_fail_detailed(pay->cmd, PAY_ROUTE_NOT_FOUND, NULL, - "Could not find a route"); - return; - } - - msatoshi_sent = route[0].amount; - fee = msatoshi_sent - pay->msatoshi; - /* FIXME: IEEE Double-precision floating point has only 53 bits - * of precision. Total satoshis that can ever be created is - * slightly less than 2100000000000000. Total msatoshis that - * can ever be created is 1000 times that or - * 2100000000000000000, requiring 60.865 bits of precision, - * and thus losing precision in the below. Currently, OK, as, - * payments are limited to 4294967295 msatoshi. */ - feepercent = ((double) fee) * 100.0 / ((double) pay->msatoshi); - if (feepercent > pay->maxfeepercent) { - data = new_json_result(pay); - json_object_start(data, NULL); - json_add_u64(data, "fee", fee); - json_add_double(data, "feepercent", feepercent); - json_add_u64(data, "msatoshi", pay->msatoshi); - json_add_double(data, "maxfeepercent", pay->maxfeepercent); - json_object_end(data); - - command_fail_detailed(pay->cmd, PAY_ROUTE_TOO_EXPENSIVE, - data, - "Fee %"PRIu64" is %f%% " - "of payment %"PRIu64"; " - "max fee requested is %f%%", - fee, feepercent, - pay->msatoshi, - pay->maxfeepercent); - return; - } - - send_payment(pay->cmd, &pay->payment_hash, route); -} - -static void json_pay(struct command *cmd, - const char *buffer, const jsmntok_t *params) -{ - jsmntok_t *bolt11tok, *msatoshitok, *desctok, *riskfactortok, *maxfeetok; - double riskfactor = 1.0; - double maxfeepercent = 0.5; - u64 msatoshi; - struct pay *pay = tal(cmd, struct pay); - struct bolt11 *b11; - char *fail, *b11str, *desc; - u8 *req; - - if (!json_get_params(cmd, buffer, params, - "bolt11", &bolt11tok, - "?msatoshi", &msatoshitok, - "?description", &desctok, - "?riskfactor", &riskfactortok, - "?maxfeepercent", &maxfeetok, - NULL)) { - return; - } - - b11str = tal_strndup(cmd, buffer + bolt11tok->start, - bolt11tok->end - bolt11tok->start); - if (desctok) - desc = tal_strndup(cmd, buffer + desctok->start, - desctok->end - desctok->start); - else - desc = NULL; - - b11 = bolt11_decode(pay, b11str, desc, &fail); - if (!b11) { - command_fail(cmd, "Invalid bolt11: %s", fail); - return; - } - - pay->cmd = cmd; - pay->payment_hash = b11->payment_hash; - - if (b11->msatoshi) { - msatoshi = *b11->msatoshi; - if (msatoshitok) { - command_fail(cmd, "msatoshi parameter unnecessary"); - return; - } - } else { - if (!msatoshitok) { - command_fail(cmd, "msatoshi parameter required"); - return; - } - if (!json_tok_u64(buffer, msatoshitok, &msatoshi)) { - command_fail(cmd, - "msatoshi '%.*s' is not a valid number", - (int)(msatoshitok->end-msatoshitok->start), - buffer + msatoshitok->start); - return; - } - } - pay->msatoshi = msatoshi; - - if (riskfactortok - && !json_tok_double(buffer, riskfactortok, &riskfactor)) { - command_fail(cmd, "'%.*s' is not a valid double", - (int)(riskfactortok->end - riskfactortok->start), - buffer + riskfactortok->start); - return; - } - - if (maxfeetok - && !json_tok_double(buffer, maxfeetok, &maxfeepercent)) { - command_fail(cmd, "'%.*s' is not a valid double", - (int)(maxfeetok->end - maxfeetok->start), - buffer + maxfeetok->start); - return; - } - /* Ensure it is in range 0.0 <= maxfeepercent <= 100.0 */ - if (!(0.0 <= maxfeepercent)) { - command_fail(cmd, "%f maxfeepercent must be non-negative", - maxfeepercent); - return; - } - if (!(maxfeepercent <= 100.0)) { - command_fail(cmd, "%f maxfeepercent must be <= 100.0", - maxfeepercent); - return; - } - pay->maxfeepercent = maxfeepercent; - - /* FIXME: use b11->routes */ - req = towire_gossip_getroute_request(cmd, &cmd->ld->id, - &b11->receiver_id, - msatoshi, riskfactor*1000, - b11->min_final_cltv_expiry); - subd_req(pay, cmd->ld->gossip, req, -1, 0, json_pay_getroute_reply, pay); - command_still_pending(cmd); -} - -static const struct json_command pay_command = { - "pay", - json_pay, - "Send payment specified by {bolt11} with optional {msatoshi} " - "(if and only if {bolt11} does not have amount), " - "{description} (required if {bolt11} uses description hash), " - "{riskfactor} (default 1.0), and " - "{maxfeepercent} (default 0.5) the maximum acceptable fee as a percentage (e.g. 0.5 => 0.5%)" -}; -AUTODATA(json_command, &pay_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 b8fe64d7f724..152e5c8856a3 100644 --- a/lightningd/pay.h +++ b/lightningd/pay.h @@ -1,12 +1,51 @@ #ifndef LIGHTNING_LIGHTNINGD_PAY_H #define LIGHTNING_LIGHTNINGD_PAY_H #include "config.h" +#include +#include +#include #include struct htlc_out; struct lightningd; -struct preimage; -struct pubkey; +struct route_hop; +struct sha256; + +/* Routing failure object */ +struct routing_failure { + unsigned int erring_index; + enum onion_type failcode; + struct pubkey erring_node; + struct short_channel_id erring_channel; + u8 *channel_update; +}; + +/* Result of send_payment */ +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; +}; + +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); void payment_succeeded(struct lightningd *ld, struct htlc_out *hout, const struct preimage *rval); diff --git a/lightningd/payalgo.c b/lightningd/payalgo.c new file mode 100644 index 000000000000..b109099c035f --- /dev/null +++ b/lightningd/payalgo.c @@ -0,0 +1,332 @@ +#include "pay.h" +#include "payalgo.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct pay { + /* Parent command. */ + struct command *cmd; + + /* Bolt11 details */ + struct sha256 payment_hash; + struct pubkey receiver_id; + struct timeabs expiry; + u32 min_final_cltv_expiry; + + /* Command details */ + u64 msatoshi; + double riskfactor; + double maxfeepercent; + + /* Number of payment tries */ + unsigned int tries; + + /* Parent of the sendpay object. */ + char *sendpay_parent; +}; + +static void +json_pay_success(struct command *cmd, + const struct preimage *payment_preimage, + unsigned int tries) +{ + 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_add_num(response, "tries", tries); + json_object_end(response); + command_success(cmd, response); +} + +static void json_pay_failure(struct command *cmd, + const struct sendpay_result *r) +{ + struct json_result *data; + const char *msg; + struct routing_failure *fail; + + assert(!r->succeeded); + + /* FIXME: can probably be factored out with similar code + * in lightningd/pay.c */ + 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); +} + +/* Start a payment attempt. */ +static void json_pay_try(struct pay *pay); + +/* Call when sendpay returns to us. */ +static void json_pay_sendpay_resolve(const struct sendpay_result *r, + void *vpay) +{ + struct pay *pay = (struct pay *) vpay; + struct timeabs now = time_now(); + + /* If we succeed, hurray */ + if (r->succeeded) { + json_pay_success(pay->cmd, &r->preimage, pay->tries); + return; + } + + /* We can retry only if it is one of the retryable errors + * below. If it is not, fail now. */ + if (r->errorcode != PAY_UNPARSEABLE_ONION && + r->errorcode != PAY_TRY_OTHER_ROUTE) { + json_pay_failure(pay->cmd, r); + return; + } + + /* If too late anyway, fail now. */ + if (time_after(now, pay->expiry)) { + /* FIXME: maybe another error kind? */ + json_pay_failure(pay->cmd, r); + return; + } + + json_pay_try(pay); +} + +static void json_pay_getroute_reply(struct subd *gossip, + const u8 *reply, const int *fds, + struct pay *pay) +{ + struct route_hop *route; + u64 msatoshi_sent; + u64 fee; + double feepercent; + struct json_result *data; + + fromwire_gossip_getroute_reply(reply, reply, NULL, &route); + + if (tal_count(route) == 0) { + command_fail_detailed(pay->cmd, PAY_ROUTE_NOT_FOUND, NULL, + "Could not find a route"); + return; + } + + msatoshi_sent = route[0].amount; + fee = msatoshi_sent - pay->msatoshi; + /* FIXME: IEEE Double-precision floating point has only 53 bits + * of precision. Total satoshis that can ever be created is + * slightly less than 2100000000000000. Total msatoshis that + * can ever be created is 1000 times that or + * 2100000000000000000, requiring 60.865 bits of precision, + * and thus losing precision in the below. Currently, OK, as, + * payments are limited to 4294967295 msatoshi. */ + feepercent = ((double) fee) * 100.0 / ((double) pay->msatoshi); + if (feepercent > pay->maxfeepercent) { + data = new_json_result(pay); + json_object_start(data, NULL); + json_add_u64(data, "fee", fee); + json_add_double(data, "feepercent", feepercent); + json_add_u64(data, "msatoshi", pay->msatoshi); + json_add_double(data, "maxfeepercent", pay->maxfeepercent); + json_object_end(data); + + command_fail_detailed(pay->cmd, PAY_ROUTE_TOO_EXPENSIVE, + data, + "Fee %"PRIu64" is %f%% " + "of payment %"PRIu64"; " + "max fee requested is %f%%", + fee, feepercent, + pay->msatoshi, + pay->maxfeepercent); + return; + } + + send_payment(pay->sendpay_parent, + pay->cmd->ld, &pay->payment_hash, route, + &json_pay_sendpay_resolve, pay); +} + +/* Start a payment attempt */ +static void json_pay_try(struct pay *pay) +{ + u8 *req; + struct command *cmd = pay->cmd; + + /* Clear previous sendpay. */ + pay->sendpay_parent = tal_free(pay->sendpay_parent); + pay->sendpay_parent = tal(pay, char); + + ++pay->tries; + + /* FIXME: use b11->routes */ + req = towire_gossip_getroute_request(cmd, &cmd->ld->id, + &pay->receiver_id, + pay->msatoshi, + pay->riskfactor, + pay->min_final_cltv_expiry); + subd_req(pay, cmd->ld->gossip, req, -1, 0, json_pay_getroute_reply, pay); +} + +static void json_pay(struct command *cmd, + const char *buffer, const jsmntok_t *params) +{ + jsmntok_t *bolt11tok, *msatoshitok, *desctok, *riskfactortok, *maxfeetok; + double riskfactor = 1.0; + double maxfeepercent = 0.5; + u64 msatoshi; + struct pay *pay = tal(cmd, struct pay); + struct bolt11 *b11; + char *fail, *b11str, *desc; + + if (!json_get_params(cmd, buffer, params, + "bolt11", &bolt11tok, + "?msatoshi", &msatoshitok, + "?description", &desctok, + "?riskfactor", &riskfactortok, + "?maxfeepercent", &maxfeetok, + NULL)) { + return; + } + + b11str = tal_strndup(cmd, buffer + bolt11tok->start, + bolt11tok->end - bolt11tok->start); + if (desctok) + desc = tal_strndup(cmd, buffer + desctok->start, + desctok->end - desctok->start); + else + desc = NULL; + + b11 = bolt11_decode(pay, b11str, desc, &fail); + if (!b11) { + command_fail(cmd, "Invalid bolt11: %s", fail); + return; + } + + pay->cmd = cmd; + pay->payment_hash = b11->payment_hash; + pay->receiver_id = b11->receiver_id; + memset(&pay->expiry, 0, sizeof(pay->expiry)); + pay->expiry.ts.tv_sec = b11->timestamp + b11->expiry; + pay->min_final_cltv_expiry = b11->min_final_cltv_expiry; + + if (b11->msatoshi) { + msatoshi = *b11->msatoshi; + if (msatoshitok) { + command_fail(cmd, "msatoshi parameter unnecessary"); + return; + } + } else { + if (!msatoshitok) { + command_fail(cmd, "msatoshi parameter required"); + return; + } + if (!json_tok_u64(buffer, msatoshitok, &msatoshi)) { + command_fail(cmd, + "msatoshi '%.*s' is not a valid number", + (int)(msatoshitok->end-msatoshitok->start), + buffer + msatoshitok->start); + return; + } + } + pay->msatoshi = msatoshi; + + if (riskfactortok + && !json_tok_double(buffer, riskfactortok, &riskfactor)) { + command_fail(cmd, "'%.*s' is not a valid double", + (int)(riskfactortok->end - riskfactortok->start), + buffer + riskfactortok->start); + return; + } + pay->riskfactor = riskfactor * 1000; + + if (maxfeetok + && !json_tok_double(buffer, maxfeetok, &maxfeepercent)) { + command_fail(cmd, "'%.*s' is not a valid double", + (int)(maxfeetok->end - maxfeetok->start), + buffer + maxfeetok->start); + return; + } + /* Ensure it is in range 0.0 <= maxfeepercent <= 100.0 */ + if (!(0.0 <= maxfeepercent)) { + command_fail(cmd, "%f maxfeepercent must be non-negative", + maxfeepercent); + return; + } + if (!(maxfeepercent <= 100.0)) { + command_fail(cmd, "%f maxfeepercent must be <= 100.0", + maxfeepercent); + return; + } + pay->maxfeepercent = maxfeepercent; + + pay->tries = 0; + pay->sendpay_parent = NULL; + + /* Initiate payment */ + json_pay_try(pay); + command_still_pending(cmd); +} + +static const struct json_command pay_command = { + "pay", + json_pay, + "Send payment specified by {bolt11} with optional {msatoshi} " + "(if and only if {bolt11} does not have amount), " + "{description} (required if {bolt11} uses description hash), " + "{riskfactor} (default 1.0), and " + "{maxfeepercent} (default 0.5) the maximum acceptable fee as a percentage (e.g. 0.5 => 0.5%)" +}; +AUTODATA(json_command, &pay_command); diff --git a/lightningd/payalgo.h b/lightningd/payalgo.h new file mode 100644 index 000000000000..971b80ba29e2 --- /dev/null +++ b/lightningd/payalgo.h @@ -0,0 +1,5 @@ +#ifndef LIGHTNING_LIGHTNINGD_PAYALGO_H +#define LIGHTNING_LIGHTNINGD_PAYALGO_H +#include "config.h" + +#endif /* LIGHTNING_LIGHTNINGD_PAYALGO_H */ diff --git a/tests/test_lightningd.py b/tests/test_lightningd.py index be6b510c4aff..fdc7894c75ce 100644 --- a/tests/test_lightningd.py +++ b/tests/test_lightningd.py @@ -823,11 +823,19 @@ def test_pay0(self): self.wait_for_routes(l1, [chanid]) # Get any-amount invoice - inv = l2.rpc.invoice("any", "any", 'description')['bolt11'] + inv = l2.rpc.invoice("any", "any", 'description') + rhash = inv['payment_hash'] + + routestep = { + 'msatoshi' : 0, + 'id' : l2.info['id'], + 'delay' : 10, + 'channel' : chanid + } # Amount must be nonzero! self.assertRaisesRegex(ValueError, 'WIRE_AMOUNT_BELOW_MINIMUM', - l1.rpc.pay, inv, 0) + l1.rpc.sendpay, to_json([routestep]), rhash) def test_pay(self): l1,l2 = self.connect() @@ -901,21 +909,6 @@ def fund_from_to_payer(lsrc, ldst, lpayer): c = self.fund_channel(lsrc, ldst, 10000000) self.wait_for_routes(lpayer, [c]) - # FIXME: This repeated retrying should get implemented - # in lightningd `pay` command directly. - def pay(l, inv): - start_time = time.time() - retry = True - err = None - while retry: - if time.time() > start_time + 10: - raise err - try: - l.rpc.pay(inv) - retry = False - except Exception as errx: - err = errx - ## Setup # Construct lightningd l1 = self.node_factory.get_node() @@ -936,7 +929,7 @@ def pay(l, inv): ## Test inv = l4.rpc.invoice(1234567, 'inv', 'for testing')['bolt11'] - pay(l1, inv) + l1.rpc.pay(inv) def test_bad_opening(self): # l1 asks for a too-long locktime