Browse Source

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 <rusty@rustcorp.com.au>
ppa-0.6.1
Rusty Russell 9 years ago
parent
commit
2610799bda
  1. 11
      daemon/json.c
  2. 6
      daemon/json.h
  3. 3
      daemon/jsonrpc.c
  4. 3
      daemon/jsonrpc.h
  5. 4
      daemon/lightning-cli.c
  6. 24
      daemon/onion.c
  7. 5
      daemon/onion.h
  8. 207
      daemon/pay.c
  9. 23
      daemon/peer.c
  10. 19
      daemon/test/test.sh

11
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;

6
daemon/json.h

@ -1,6 +1,7 @@
#ifndef LIGHTNING_DAEMON_JSON_H
#define LIGHTNING_DAEMON_JSON_H
#include "config.h"
#include <bitcoin/pubkey.h>
#include <ccan/tal/tal.h>
#include <stdbool.h>
#include <stdint.h>
@ -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, ...);

3
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,

3
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 */

4
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

24
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);
}

5
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 */

207
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"
};

23
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);

19
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

Loading…
Cancel
Save