diff --git a/lightningd/pay.c b/lightningd/pay.c index ecdc00ab8..38fa52cae 100644 --- a/lightningd/pay.c +++ b/lightningd/pay.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -143,128 +144,45 @@ static void pay_command_destroyed(struct pay_command *pc) list_del(&pc->list); } -static void json_sendpay(struct command *cmd, - const char *buffer, const jsmntok_t *params) +static void send_payment(struct command *cmd, + const struct sha256 *rhash, + const struct route_hop *route) { - struct pubkey *ids; - jsmntok_t *routetok, *rhashtok; - const jsmntok_t *t, *end; - unsigned int delay, base_expiry; - size_t n_hops; - struct sha256 rhash; - struct peer *peer; struct pay_command *pc; + struct peer *peer; const u8 *onion; u8 sessionkey[32]; - struct hop_data *hop_data; - struct hop_data first_hop_data; - u64 amount, lastamount; + unsigned int base_expiry; struct onionpacket *packet; struct secret *path_secrets; enum onion_type failcode; - - if (!json_get_params(buffer, params, - "route", &routetok, - "rhash", &rhashtok, - NULL)) { - command_fail(cmd, "Need route and rhash"); - return; - } - - if (!hex_decode(buffer + rhashtok->start, - rhashtok->end - rhashtok->start, - &rhash, sizeof(rhash))) { - command_fail(cmd, "'%.*s' is not a valid sha256 hash", - (int)(rhashtok->end - rhashtok->start), - buffer + rhashtok->start); - return; - } - - if (routetok->type != JSMN_ARRAY) { - command_fail(cmd, "'%.*s' is not an array", - (int)(routetok->end - routetok->start), - buffer + routetok->start); - return; - } + size_t i, n_hops = tal_count(route); + struct hop_data *hop_data = tal_arr(cmd, struct hop_data, n_hops); + struct pubkey *ids = tal_arr(cmd, struct pubkey, n_hops); /* Expiry for HTLCs is absolute. And add one to give some margin. */ base_expiry = get_block_height(cmd->ld->topology) + 1; - end = json_next(routetok); - n_hops = 0; - ids = tal_arr(cmd, struct pubkey, n_hops); - - hop_data = tal_arr(cmd, struct hop_data, n_hops); - for (t = routetok + 1; t < end; t = json_next(t)) { - const jsmntok_t *amttok, *idtok, *delaytok, *chantok; + /* Extract IDs for each hop: create_onionpacket wants array. */ + for (i = 0; i < n_hops; i++) + ids[i] = route[i].nodeid; - if (t->type != JSMN_OBJECT) { - command_fail(cmd, "route %zu '%.*s' is not an object", - n_hops, - (int)(t->end - t->start), - buffer + t->start); - return; - } - amttok = json_get_member(buffer, t, "msatoshi"); - idtok = json_get_member(buffer, t, "id"); - delaytok = json_get_member(buffer, t, "delay"); - chantok = json_get_member(buffer, t, "channel"); - if (!amttok || !idtok || !delaytok || !chantok) { - command_fail(cmd, "route %zu needs msatoshi/id/channel/delay", - n_hops); - return; - } - - tal_resize(&hop_data, n_hops + 1); - tal_resize(&ids, n_hops+1); - hop_data[n_hops].realm = 0; - /* What that hop will forward */ - if (!json_tok_u64(buffer, amttok, &amount)) { - command_fail(cmd, "route %zu invalid msatoshi", - n_hops); - return; - } - hop_data[n_hops].amt_forward = amount; - - if (!short_channel_id_from_str(buffer + chantok->start, - chantok->end - chantok->start, - &hop_data[n_hops].channel_id)) { - command_fail(cmd, "route %zu invalid channel_id", n_hops); - return; - } - if (!json_tok_pubkey(buffer, idtok, &ids[n_hops])) { - command_fail(cmd, "route %zu invalid id", n_hops); - return; - } - if (!json_tok_number(buffer, delaytok, &delay)) { - command_fail(cmd, "route %zu invalid delay", n_hops); - return; - } - hop_data[n_hops].outgoing_cltv = base_expiry + delay; - n_hops++; - } - - if (n_hops == 0) { - command_fail(cmd, "Empty route"); - return; + /* Copy hop_data[n] from route[n+1] (ie. where it goes next) */ + for (i = 0; i < n_hops - 1; i++) { + hop_data[i].realm = 0; + hop_data[i].channel_id = route[i+1].channel_id; + hop_data[i].amt_forward = route[i+1].amount; + hop_data[i].outgoing_cltv = base_expiry + route[i+1].delay; } - /* Store some info we'll need for our own HTLC */ - amount = hop_data[0].amt_forward; - lastamount = hop_data[n_hops-1].amt_forward; - first_hop_data = hop_data[0]; - - /* Shift the hop_data down by one, so each hop gets its - * instructions, not how we got there */ - for (size_t i=0; i < n_hops - 1; i++) { - hop_data[i] = hop_data[i+1]; - } /* And finally set the final hop to the special values in * BOLT04 */ - hop_data[n_hops-1].outgoing_cltv = base_expiry + delay; - memset(&hop_data[n_hops-1].channel_id, 0, sizeof(struct short_channel_id)); + hop_data[i].realm = 0; + hop_data[i].outgoing_cltv = base_expiry + route[i].delay; + memset(&hop_data[i].channel_id, 0, sizeof(struct short_channel_id)); + hop_data[i].amt_forward = route[i].amount; - pc = find_pay_command(cmd->ld, &rhash); + pc = find_pay_command(cmd->ld, rhash); if (pc) { log_debug(cmd->ld->log, "json_sendpay: found previous"); if (pc->out) { @@ -276,7 +194,7 @@ static void json_sendpay(struct command *cmd, size_t old_nhops = tal_count(pc->ids); log_add(cmd->ld->log, "... succeeded"); /* Must match successful payment parameters. */ - if (pc->msatoshi != lastamount) { + if (pc->msatoshi != hop_data[n_hops-1].amt_forward) { command_fail(cmd, "already succeeded with amount %" PRIu64, pc->msatoshi); @@ -307,7 +225,7 @@ static void json_sendpay(struct command *cmd, 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(cmd, ids, hop_data, sessionkey, rhash->u.u8, sizeof(struct sha256), &path_secrets); onion = serialize_onionpacket(cmd, packet); @@ -319,14 +237,14 @@ static void json_sendpay(struct command *cmd, tal_add_destructor(pc, pay_command_destroyed); } pc->cmd = cmd; - pc->rhash = rhash; + pc->rhash = *rhash; pc->rval = NULL; pc->ids = tal_steal(pc, ids); - pc->msatoshi = lastamount; + pc->msatoshi = route[n_hops-1].amount; pc->path_secrets = tal_steal(pc, path_secrets); - log_info(cmd->ld->log, "Sending %"PRIu64" over %zu hops to deliver %"PRIu64, - amount, n_hops, lastamount); + log_info(cmd->ld->log, "Sending %u over %zu hops to deliver %"PRIu64, + route[0].amount, n_hops, pc->msatoshi); /* Wait until we get response. */ tal_add_destructor2(cmd, remove_cmd_from_pc, pc); @@ -336,8 +254,9 @@ static void json_sendpay(struct command *cmd, * remove_cmd_from_pc destructor causes a use-after-free */ tal_steal(pc, cmd); - failcode = send_htlc_out(peer, amount, first_hop_data.outgoing_cltv, - &rhash, onion, NULL, pc, &pc->out); + failcode = send_htlc_out(peer, route[0].amount, + base_expiry + route[0].delay, + rhash, onion, NULL, pc, &pc->out); if (failcode) { command_fail(cmd, "first peer not ready: %s", onion_type_name(failcode)); @@ -345,6 +264,97 @@ static void json_sendpay(struct command *cmd, } } +static void json_sendpay(struct command *cmd, + const char *buffer, const jsmntok_t *params) +{ + jsmntok_t *routetok, *rhashtok; + const jsmntok_t *t, *end; + size_t n_hops; + struct sha256 rhash; + struct route_hop *route; + + if (!json_get_params(buffer, params, + "route", &routetok, + "rhash", &rhashtok, + NULL)) { + command_fail(cmd, "Need route and rhash"); + return; + } + + if (!hex_decode(buffer + rhashtok->start, + rhashtok->end - rhashtok->start, + &rhash, sizeof(rhash))) { + command_fail(cmd, "'%.*s' is not a valid sha256 hash", + (int)(rhashtok->end - rhashtok->start), + buffer + rhashtok->start); + return; + } + + if (routetok->type != JSMN_ARRAY) { + command_fail(cmd, "'%.*s' is not an array", + (int)(routetok->end - routetok->start), + buffer + routetok->start); + return; + } + + end = json_next(routetok); + n_hops = 0; + route = tal_arr(cmd, struct route_hop, n_hops); + + for (t = routetok + 1; t < end; t = json_next(t)) { + const jsmntok_t *amttok, *idtok, *delaytok, *chantok; + + if (t->type != JSMN_OBJECT) { + command_fail(cmd, "route %zu '%.*s' is not an object", + n_hops, + (int)(t->end - t->start), + buffer + t->start); + return; + } + amttok = json_get_member(buffer, t, "msatoshi"); + idtok = json_get_member(buffer, t, "id"); + delaytok = json_get_member(buffer, t, "delay"); + chantok = json_get_member(buffer, t, "channel"); + if (!amttok || !idtok || !delaytok || !chantok) { + command_fail(cmd, "route %zu needs msatoshi/id/channel/delay", + n_hops); + return; + } + + tal_resize(&route, n_hops + 1); + + /* What that hop will forward */ + if (!json_tok_number(buffer, amttok, &route[n_hops].amount)) { + command_fail(cmd, "route %zu invalid msatoshi", + n_hops); + return; + } + + if (!short_channel_id_from_str(buffer + chantok->start, + chantok->end - chantok->start, + &route[n_hops].channel_id)) { + command_fail(cmd, "route %zu invalid channel_id", n_hops); + return; + } + if (!json_tok_pubkey(buffer, idtok, &route[n_hops].nodeid)) { + command_fail(cmd, "route %zu invalid id", n_hops); + return; + } + if (!json_tok_number(buffer, delaytok, &route[n_hops].delay)) { + command_fail(cmd, "route %zu invalid delay", n_hops); + return; + } + n_hops++; + } + + if (n_hops == 0) { + command_fail(cmd, "Empty route"); + return; + } + + send_payment(cmd, &rhash, route); +} + static const struct json_command sendpay_command = { "sendpay", json_sendpay, diff --git a/tests/test_lightningd.py b/tests/test_lightningd.py index 28b9a6c5a..00fefd16e 100644 --- a/tests/test_lightningd.py +++ b/tests/test_lightningd.py @@ -309,8 +309,6 @@ class LightningDTests(BaseLightningDTests): self.fund_channel(l1, l2, 10**6) - time.sleep(5) - amt = 200000000 rhash = l2.rpc.invoice(amt, 'testpayment2')['rhash'] assert l2.rpc.listinvoice('testpayment2')[0]['complete'] == False