From 2610799bda23a659279f24f6c32de3383703bce7 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 31 Aug 2016 16:06:08 +0930 Subject: [PATCH] pay: split into getroute and sendpay This is less convenient to use, but makes far more sense for a real user (like a wallet). It can ask about the route, then decide whether to use it or not. This will make even more sense once we add a parameter to control how long we let the HTLC be delayed for, so a client can query for high, medium and low tolerances and compare results. Signed-off-by: Rusty Russell --- daemon/json.c | 11 +++ daemon/json.h | 6 ++ daemon/jsonrpc.c | 3 +- daemon/jsonrpc.h | 3 +- daemon/lightning-cli.c | 4 +- daemon/onion.c | 24 ++--- daemon/onion.h | 5 +- daemon/pay.c | 207 +++++++++++++++++++++++++++++++++-------- daemon/peer.c | 23 +---- daemon/test/test.sh | 19 ++-- 10 files changed, 218 insertions(+), 87 deletions(-) diff --git a/daemon/json.c b/daemon/json.c index dfcdd34ce..f77f742d4 100644 --- a/daemon/json.c +++ b/daemon/json.c @@ -425,6 +425,17 @@ void json_add_hex(struct json_result *result, const char *fieldname, json_add_string(result, fieldname, hex); } +void json_add_pubkey(struct json_result *response, + secp256k1_context *secpctx, + const char *fieldname, + const struct pubkey *key) +{ + u8 der[PUBKEY_DER_LEN]; + + pubkey_to_der(secpctx, der, key); + json_add_hex(response, fieldname, der, sizeof(der)); +} + void json_add_object(struct json_result *result, ...) { va_list ap; diff --git a/daemon/json.h b/daemon/json.h index 532fece92..84b60d308 100644 --- a/daemon/json.h +++ b/daemon/json.h @@ -1,6 +1,7 @@ #ifndef LIGHTNING_DAEMON_JSON_H #define LIGHTNING_DAEMON_JSON_H #include "config.h" +#include #include #include #include @@ -98,6 +99,11 @@ void json_add_null(struct json_result *result, const char *fieldname); /* '"fieldname" : "0189abcdef..."' or "0189abcdef..." if fieldname is NULL */ void json_add_hex(struct json_result *result, const char *fieldname, const void *data, size_t len); +/* '"fieldname" : "0289abcdef..."' or "0289abcdef..." if fieldname is NULL */ +void json_add_pubkey(struct json_result *response, + secp256k1_context *secpctx, + const char *fieldname, + const struct pubkey *key); void json_add_object(struct json_result *result, ...); diff --git a/daemon/jsonrpc.c b/daemon/jsonrpc.c index fd5911270..87c6746a8 100644 --- a/daemon/jsonrpc.c +++ b/daemon/jsonrpc.c @@ -290,7 +290,8 @@ static const struct json_command *cmdlist[] = { &close_command, &newaddr_command, &accept_payment_command, - &pay_command, + &getroute_command, + &sendpay_command, &feerate_command, /* Developer/debugging options. */ &echo_command, diff --git a/daemon/jsonrpc.h b/daemon/jsonrpc.h index bd07df1dd..1ccd00631 100644 --- a/daemon/jsonrpc.h +++ b/daemon/jsonrpc.h @@ -74,6 +74,7 @@ extern const struct json_command output_command; extern const struct json_command accept_payment_command; extern const struct json_command add_route_command; extern const struct json_command routefail_command; -extern const struct json_command pay_command; +extern const struct json_command getroute_command; +extern const struct json_command sendpay_command; extern const struct json_command feerate_command; #endif /* LIGHTNING_DAEMON_JSONRPC_H */ diff --git a/daemon/lightning-cli.c b/daemon/lightning-cli.c index e077f65ec..49dd43325 100644 --- a/daemon/lightning-cli.c +++ b/daemon/lightning-cli.c @@ -96,11 +96,13 @@ int main(int argc, char *argv[]) method, idstr); for (i = 2; i < argc; i++) { - /* Numbers and bools are left unquoted, + /* Numbers, bools, objects and arrays are left unquoted, * and quoted things left alone. */ if (strspn(argv[i], "0123456789") == strlen(argv[i]) || streq(argv[i], "true") || streq(argv[i], "false") + || argv[i][0] == '{' + || argv[i][0] == '[' || argv[i][0] == '"') tal_append_fmt(&cmd, "%s", argv[i]); else diff --git a/daemon/onion.c b/daemon/onion.c index 7fae43f12..5e6238134 100644 --- a/daemon/onion.c +++ b/daemon/onion.c @@ -16,41 +16,35 @@ static const u8 *to_onion(const tal_t *ctx, const Route *r) return onion; } -/* Create an onion for sending msatoshi_with_fees down path. */ +/* Create an onion for this path. */ const u8 *onion_create(const tal_t *ctx, secp256k1_context *secpctx, - struct node_connection **path, - u64 msatoshi, s64 fees) + const struct pubkey *ids, + const u64 *amounts, + size_t num_hops) { Route *r = tal(ctx, Route); - int i; - u64 amount = msatoshi; + size_t i; route__init(r); - r->n_steps = tal_count(path) + 1; + r->n_steps = num_hops + 1; r->steps = tal_arr(r, RouteStep *, r->n_steps); - /* Create backwards, so we can get fees correct. */ - for (i = tal_count(path) - 1; i >= 0; i--) { + for (i = 0; i < num_hops; i++) { r->steps[i] = tal(r, RouteStep); route_step__init(r->steps[i]); r->steps[i]->next_case = ROUTE_STEP__NEXT_BITCOIN; - r->steps[i]->bitcoin = pubkey_to_proto(r, secpctx, - &path[i]->dst->id); - r->steps[i]->amount = amount; - amount += connection_fee(path[i], amount); + r->steps[i]->bitcoin = pubkey_to_proto(r, secpctx, &ids[i]); + r->steps[i]->amount = amounts[i]; } /* Now the stop marker. */ - i = tal_count(path); r->steps[i] = tal(r, RouteStep); route_step__init(r->steps[i]); r->steps[i]->next_case = ROUTE_STEP__NEXT_END; r->steps[i]->end = true; r->steps[i]->amount = 0; - assert(amount == msatoshi + fees); - return to_onion(ctx, r); } diff --git a/daemon/onion.h b/daemon/onion.h index f993ffa74..90405524e 100644 --- a/daemon/onion.h +++ b/daemon/onion.h @@ -15,6 +15,7 @@ RouteStep *onion_unwrap(struct peer *peer, /* Create an onion for sending msatoshi down path, paying fees. */ const u8 *onion_create(const tal_t *ctx, secp256k1_context *secpctx, - struct node_connection **path, - u64 msatoshi, s64 fees); + const struct pubkey *ids, + const u64 *amounts, + size_t num_hops); #endif /* LIGHTNING_DAEMON_ONION_H */ diff --git a/daemon/pay.c b/daemon/pay.c index 524b8c1c0..6b8791d87 100644 --- a/daemon/pay.c +++ b/daemon/pay.c @@ -15,7 +15,7 @@ struct pay_command { struct list_node list; struct sha256 rhash; - u64 msatoshis, fee; + u64 msatoshis; struct pubkey id; /* Set if this is in progress. */ struct htlc *htlc; @@ -110,29 +110,37 @@ static struct pay_command *find_pay_command(struct lightningd_state *dstate, return NULL; } -static void json_pay(struct command *cmd, - const char *buffer, const jsmntok_t *params) +static void json_add_route(struct json_result *response, + secp256k1_context *secpctx, + const struct pubkey *id, + u64 amount, unsigned int delay) +{ + json_object_start(response, NULL); + json_add_pubkey(response, secpctx, "id", id); + json_add_u64(response, "msatoshis", amount); + json_add_num(response, "delay", delay); + json_object_end(response); +} + +static void json_getroute(struct command *cmd, + const char *buffer, const jsmntok_t *params) { struct pubkey id; - jsmntok_t *idtok, *msatoshistok, *rhashtok; - unsigned int expiry; + jsmntok_t *idtok, *msatoshistok; + struct json_result *response; int i; u64 msatoshis; s64 fee; - struct sha256 rhash; struct node_connection **route; struct peer *peer; - struct pay_command *pc; - const u8 *onion; - enum fail_error error_code; - const char *err; + u64 *amounts, total_amount; + unsigned int total_delay, *delays; if (!json_get_params(buffer, params, "id", &idtok, "msatoshis", &msatoshistok, - "rhash", &rhashtok, NULL)) { - command_fail(cmd, "Need id, msatoshis and rhash"); + command_fail(cmd, "Need id and msatoshis"); return; } @@ -150,6 +158,79 @@ static void json_pay(struct command *cmd, return; } + peer = find_route(cmd->dstate, &id, msatoshis, &fee, &route); + if (!peer) { + command_fail(cmd, "no route found"); + return; + } + + /* Fees, delays need to be calculated backwards along route. */ + amounts = tal_arr(cmd, u64, tal_count(route)+1); + delays = tal_arr(cmd, unsigned int, tal_count(route)+1); + total_amount = msatoshis; + + total_delay = 0; + for (i = tal_count(route) - 1; i >= 0; i--) { + amounts[i+1] = total_amount; + total_amount += connection_fee(route[i], total_amount); + + total_delay += route[i]->delay; + if (total_delay < route[i]->min_blocks) + total_delay = route[i]->min_blocks; + delays[i+1] = total_delay; + } + /* We don't charge ourselves any fees. */ + amounts[0] = total_amount; + /* We do require delay though. */ + total_delay += peer->nc->delay; + if (total_delay < peer->nc->min_blocks) + total_delay = peer->nc->min_blocks; + delays[0] = total_delay; + + response = new_json_result(cmd); + json_object_start(response, NULL); + json_array_start(response, "route"); + json_add_route(response, cmd->dstate->secpctx, + peer->id, amounts[0], delays[0]); + for (i = 0; i < tal_count(route); i++) + json_add_route(response, cmd->dstate->secpctx, + &route[i]->dst->id, amounts[i+1], delays[i+1]); + json_array_end(response); + json_object_end(response); + command_success(cmd, response); +} + +const struct json_command getroute_command = { + "getroute", + json_getroute, + "Return route for {msatoshis} to {id}", + "Returns a {route} array of {id} {msatoshis} {delay}: msatoshis and delay (in blocks) is cumulative." +}; + +static void json_sendpay(struct command *cmd, + const char *buffer, const jsmntok_t *params) +{ + struct pubkey *ids; + u64 *amounts; + jsmntok_t *routetok, *rhashtok; + const jsmntok_t *t, *end; + unsigned int delay; + size_t n_hops; + struct sha256 rhash; + struct peer *peer; + struct pay_command *pc; + const u8 *onion; + enum fail_error error_code; + const char *err; + + 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))) { @@ -159,9 +240,65 @@ static void json_pay(struct command *cmd, 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; + amounts = tal_arr(cmd, u64, n_hops); + ids = tal_arr(cmd, struct pubkey, n_hops); + for (t = routetok + 1; t < end; t = json_next(t)) { + const jsmntok_t *amttok, *idtok, *delaytok; + + 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, "msatoshis"); + idtok = json_get_member(buffer, t, "id"); + delaytok = json_get_member(buffer, t, "delay"); + if (!amttok || !idtok || !delaytok) { + command_fail(cmd, "route %zu needs msatoshis/id/delay", + n_hops); + return; + } + + tal_resize(&amounts, n_hops+1); + if (!json_tok_u64(buffer, amttok, &amounts[n_hops])) { + command_fail(cmd, "route %zu invalid msatoshis", n_hops); + return; + } + tal_resize(&ids, n_hops+1); + if (!pubkey_from_hexstr(cmd->dstate->secpctx, + buffer + idtok->start, + idtok->end - idtok->start, + &ids[n_hops])) { + command_fail(cmd, "route %zu invalid id", n_hops); + return; + } + /* Only need first delay. */ + if (n_hops == 0 && !json_tok_number(buffer, delaytok, &delay)) { + command_fail(cmd, "route %zu invalid delay", n_hops); + return; + } + n_hops++; + } + + if (n_hops == 0) { + command_fail(cmd, "Empty route"); + return; + } + pc = find_pay_command(cmd->dstate, &rhash); if (pc) { - log_debug(cmd->dstate->base_log, "json_pay: found previous"); + log_debug(cmd->dstate->base_log, "json_sendpay: found previous"); if (pc->htlc) { log_add(cmd->dstate->base_log, "... still in progress"); command_fail(cmd, "still in progress"); @@ -170,13 +307,13 @@ static void json_pay(struct command *cmd, if (pc->rval) { log_add(cmd->dstate->base_log, "... succeeded"); /* Must match successful payment parameters. */ - if (pc->msatoshis != msatoshis) { + if (pc->msatoshis != amounts[n_hops-1]) { command_fail(cmd, "already succeeded with amount %" PRIu64, pc->msatoshis); return; } - if (!structeq(&pc->id, &id)) { + if (!structeq(&pc->id, &ids[n_hops-1])) { char *previd; previd = pubkey_to_hexstr(cmd, cmd->dstate->secpctx, @@ -192,38 +329,28 @@ static void json_pay(struct command *cmd, log_add(cmd->dstate->base_log, "... retrying"); } - /* FIXME: Add fee param, check for excessive fee. */ - peer = find_route(cmd->dstate, &id, msatoshis, &fee, &route); + peer = find_peer(cmd->dstate, &ids[0]); if (!peer) { - command_fail(cmd, "no route found"); + command_fail(cmd, "no connection to first peer found"); return; } - expiry = 0; - for (i = tal_count(route) - 1; i >= 0; i--) { - expiry += route[i]->delay; - if (expiry < route[i]->min_blocks) - expiry = route[i]->min_blocks; - } - expiry += peer->nc->delay; - if (expiry < peer->nc->min_blocks) - expiry = peer->nc->min_blocks; - - /* Expiry for HTLCs is absolute. And add one to give some margin. */ - expiry += get_block_height(cmd->dstate) + 1; - - onion = onion_create(cmd, cmd->dstate->secpctx, route, msatoshis, fee); + /* Onion will carry us from first peer onwards. */ + onion = onion_create(cmd, cmd->dstate->secpctx, ids+1, amounts+1, + n_hops-1); if (!pc) pc = tal(cmd->dstate, struct pay_command); pc->cmd = cmd; pc->rhash = rhash; pc->rval = NULL; - pc->id = id; - pc->msatoshis = msatoshis; - pc->fee = fee; + pc->id = ids[n_hops-1]; + pc->msatoshis = amounts[n_hops-1]; - err = command_htlc_add(peer, msatoshis + fee, expiry, &rhash, NULL, + /* Expiry for HTLCs is absolute. And add one to give some margin. */ + err = command_htlc_add(peer, amounts[0], + delay + get_block_height(cmd->dstate) + 1, + &rhash, NULL, onion, &error_code, &pc->htlc); if (err) { command_fail(cmd, "could not add htlc: %u: %s", error_code, err); @@ -235,9 +362,9 @@ static void json_pay(struct command *cmd, tal_add_destructor(cmd, remove_cmd_from_pc); } -const struct json_command pay_command = { - "pay", - json_pay, - "Send {id} {msatoshis} in return for preimage of {rhash}", +const struct json_command sendpay_command = { + "sendpay", + json_sendpay, + "Send along {route} in return for preimage of {rhash}", "Returns the {preimage} on success" }; diff --git a/daemon/peer.c b/daemon/peer.c index 449a1a61d..a1677ff26 100644 --- a/daemon/peer.c +++ b/daemon/peer.c @@ -4169,17 +4169,6 @@ static void json_add_abstime(struct json_result *response, json_object_end(response); } -static void json_add_pubkey(struct json_result *response, - secp256k1_context *secpctx, - const char *id, - const struct pubkey *key) -{ - u8 der[PUBKEY_DER_LEN]; - - pubkey_to_der(secpctx, der, key); - json_add_hex(response, id, der, sizeof(der)); -} - static void json_add_htlcs(struct json_result *response, const char *id, struct peer *peer, @@ -4344,15 +4333,6 @@ void cleanup_peers(struct lightningd_state *dstate) } } -/* A zero-fee single route to this peer. */ -static const u8 *dummy_single_route(const tal_t *ctx, - const struct peer *peer, - u64 msatoshis) -{ - struct node_connection **path = tal_arr(ctx, struct node_connection *, 0); - return onion_create(ctx, peer->dstate->secpctx, path, msatoshis, 0); -} - static void json_newhtlc(struct command *cmd, const char *buffer, const jsmntok_t *params) { @@ -4416,7 +4396,8 @@ static void json_newhtlc(struct command *cmd, log_debug(peer->log, "JSON command to add new HTLC"); err = command_htlc_add(peer, msatoshis, expiry, &rhash, NULL, - dummy_single_route(cmd, peer, msatoshis), + onion_create(cmd, cmd->dstate->secpctx, + NULL, NULL, 0), &error_code, &htlc); if (err) { command_fail(cmd, "could not add htlc: %u:%s", error_code, err); diff --git a/daemon/test/test.sh b/daemon/test/test.sh index 99fd5bd43..0e4154463 100755 --- a/daemon/test/test.sh +++ b/daemon/test/test.sh @@ -1011,20 +1011,26 @@ if [ ! -n "$MANUALCOMMIT" ]; then # FIXME: We don't save payments in db yet! DO_RECONNECT="" + # Get route. + ROUTE=`lcli1 getroute $ID3 $HTLC_AMOUNT` + ROUTE=`echo $ROUTE | sed 's/^{ "route" : \(.*\) }$/\1/'` + # Try wrong hash. - if lcli1 pay $ID3 $HTLC_AMOUNT $RHASH4; then + if lcli1 sendpay "$ROUTE" $RHASH4; then echo Paid with wrong hash? >&2 exit 1 fi # Try underpaying. - if lcli1 pay $ID3 $(($HTLC_AMOUNT-1)) $RHASH5; then + PAID=`echo "$ROUTE" | sed -n 's/.*"msatoshis" : \([0-9]*\),.*/\1/p'` + UNDERPAY=`echo "$ROUTE" | sed "s/: $PAID,/: $(($PAID - 1)),/"` + if lcli1 sendpay "$UNDERPAY" $RHASH5; then echo Paid with too little? >&2 exit 1 fi # Pay correctly. - lcli1 pay $ID3 $HTLC_AMOUNT $RHASH5 + lcli1 sendpay "$ROUTE" $RHASH5 # Node 3 should end up with that amount (minus 1/2 tx fee) # Note that it is delayed a little, since node2 fulfils as soon as fulfill @@ -1033,11 +1039,12 @@ if [ ! -n "$MANUALCOMMIT" ]; then lcli3 close $ID2 # Re-send should be a noop (doesn't matter that node3 is down!) - lcli1 pay $ID3 $HTLC_AMOUNT $RHASH5 + lcli1 sendpay "$ROUTE" $RHASH5 # Re-send to different id or amount should complain. - lcli1 pay $ID2 $HTLC_AMOUNT $RHASH5 | $FGREP "already succeeded to $ID3" - lcli1 pay $ID2 $(($HTLC_AMOUNT + 1)) $RHASH5 | $FGREP "already succeeded with amount $HTLC_AMOUNT" + SHORTROUTE=`echo "$ROUTE" | sed 's/, { "id" : .* }//' | sed 's/"msatoshis" : [0-9]*,/"msatoshis" : '$HTLC_AMOUNT,/` + lcli1 sendpay "$SHORTROUTE" $RHASH5 | $FGREP "already succeeded to $ID3" + lcli1 sendpay "$UNDERPAY" $RHASH5 | $FGREP "already succeeded with amount $HTLC_AMOUNT" DO_RECONNECT=$RECONNECT fi