diff --git a/daemon/json.c b/daemon/json.c index 10202c499..063d066a5 100644 --- a/daemon/json.c +++ b/daemon/json.c @@ -64,6 +64,16 @@ bool json_tok_u64(const char *buffer, const jsmntok_t *tok, return true; } +bool json_tok_double(const char *buffer, const jsmntok_t *tok, double *num) +{ + char *end; + + *num = strtod(buffer + tok->start, &end); + if (end != buffer + tok->end) + return false; + return true; +} + bool json_tok_number(const char *buffer, const jsmntok_t *tok, unsigned int *num) { diff --git a/daemon/json.h b/daemon/json.h index 3f1c50f11..e6254c677 100644 --- a/daemon/json.h +++ b/daemon/json.h @@ -29,6 +29,9 @@ bool json_tok_number(const char *buffer, const jsmntok_t *tok, bool json_tok_u64(const char *buffer, const jsmntok_t *tok, uint64_t *num); +/* Extract double from this (must be a number literal) */ +bool json_tok_double(const char *buffer, const jsmntok_t *tok, double *num); + /* Extract satoshis from this (may be a string, or a decimal number literal) */ bool json_tok_bitcoin_amount(const char *buffer, const jsmntok_t *tok, uint64_t *satoshi); diff --git a/daemon/pay.c b/daemon/pay.c index f87fe7b03..9a951541d 100644 --- a/daemon/pay.c +++ b/daemon/pay.c @@ -199,11 +199,12 @@ static void json_getroute(struct command *cmd, const char *buffer, const jsmntok_t *params) { struct pubkey id; - jsmntok_t *idtok, *msatoshitok; + jsmntok_t *idtok, *msatoshitok, *riskfactortok; struct json_result *response; int i; u64 msatoshi; s64 fee; + double riskfactor; struct node_connection **route; struct peer *peer; u64 *amounts, total_amount; @@ -212,6 +213,7 @@ static void json_getroute(struct command *cmd, if (!json_get_params(buffer, params, "id", &idtok, "msatoshi", &msatoshitok, + "riskfactor", &riskfactortok, NULL)) { command_fail(cmd, "Need id and msatoshi"); return; @@ -231,7 +233,14 @@ static void json_getroute(struct command *cmd, return; } - peer = find_route(cmd->dstate, &id, msatoshi, &fee, &route); + if (!json_tok_double(buffer, riskfactortok, &riskfactor)) { + command_fail(cmd, "'%.*s' is not a valid double", + (int)(riskfactortok->end - riskfactortok->start), + buffer + riskfactortok->start); + return; + } + + peer = find_route(cmd->dstate, &id, msatoshi, riskfactor, &fee, &route); if (!peer) { command_fail(cmd, "no route found"); return; diff --git a/daemon/routing.c b/daemon/routing.c index 8dec09a09..09c8c40eb 100644 --- a/daemon/routing.c +++ b/daemon/routing.c @@ -11,6 +11,9 @@ #include #include +/* 365.25 * 24 * 60 / 10 */ +#define BLOCKS_PER_YEAR 52596 + static const secp256k1_pubkey *keyof_node(const struct node *n) { return &n->id.pubkey; @@ -197,8 +200,10 @@ static void clear_bfg(struct node_map *nodes) for (n = node_map_first(nodes, &it); n; n = node_map_next(nodes, &it)) { size_t i; - for (i = 0; i < ARRAY_SIZE(n->bfg); i++) + for (i = 0; i < ARRAY_SIZE(n->bfg); i++) { n->bfg[i].total = INFINITE; + n->bfg[i].risk = 0; + } } } @@ -213,20 +218,34 @@ s64 connection_fee(const struct node_connection *c, u64 msatoshi) return c->base_fee + fee; } +/* Risk of passing through this channel. We insert a tiny constant here + * in order to prefer shorter routes, all things equal. */ +static u64 risk_fee(s64 amount, u32 delay, double riskfactor) +{ + /* If fees are so negative we're making money, ignore risk. */ + if (amount < 0) + return 1; + + return 1 + amount * delay * riskfactor / BLOCKS_PER_YEAR / 10000; +} + /* We track totals, rather than costs. That's because the fee depends * on the current amount passing through. */ -static void bfg_one_edge(struct node *node, size_t edgenum) +static void bfg_one_edge(struct node *node, size_t edgenum, double riskfactor) { struct node_connection *c = node->in[edgenum]; size_t h; assert(c->dst == node); for (h = 0; h < ROUTING_MAX_HOPS; h++) { - /* FIXME: Bias towards smaller expiry values. */ /* FIXME: Bias against smaller channels. */ s64 fee = connection_fee(c, node->bfg[h].total); - if (node->bfg[h].total + fee < c->src->bfg[h+1].total) { + u64 risk = node->bfg[h].risk + risk_fee(node->bfg[h].total + fee, + c->delay, riskfactor); + if (node->bfg[h].total + (s64)fee + (s64)risk + < c->src->bfg[h+1].total + (s64)c->src->bfg[h+1].risk) { c->src->bfg[h+1].total = node->bfg[h].total + fee; + c->src->bfg[h+1].risk = risk; c->src->bfg[h+1].prev = c; } } @@ -234,7 +253,9 @@ static void bfg_one_edge(struct node *node, size_t edgenum) struct peer *find_route(struct lightningd_state *dstate, const struct pubkey *to, - u64 msatoshi, s64 *fee, + u64 msatoshi, + double riskfactor, + s64 *fee, struct node_connection ***route) { struct node *n, *src, *dst; @@ -258,6 +279,7 @@ struct peer *find_route(struct lightningd_state *dstate, /* Bellman-Ford-Gibson: like Bellman-Ford, but keep values for * every path length. */ src->bfg[0].total = msatoshi; + src->bfg[0].risk = 0; for (runs = 0; runs < ROUTING_MAX_HOPS; runs++) { log_debug(dstate->base_log, "Run %i", runs); @@ -267,7 +289,7 @@ struct peer *find_route(struct lightningd_state *dstate, n = node_map_next(dstate->nodes, &it)) { size_t num_edges = tal_count(n->in); for (i = 0; i < num_edges; i++) { - bfg_one_edge(n, i); + bfg_one_edge(n, i, riskfactor); log_debug(dstate->base_log, "We seek %p->%p, this is %p -> %p", dst, src, n->in[i]->src, n->in[i]->dst); log_debug_struct(dstate->base_log, diff --git a/daemon/routing.h b/daemon/routing.h index 8aac6acb7..d01a71fce 100644 --- a/daemon/routing.h +++ b/daemon/routing.h @@ -27,6 +27,8 @@ struct node { struct { /* Total to get to here from target. */ s64 total; + /* Total risk premium of this route. */ + u64 risk; /* Where that came from. */ struct node_connection *prev; } bfg[ROUTING_MAX_HOPS+1]; @@ -56,7 +58,9 @@ void remove_connection(struct lightningd_state *dstate, struct peer *find_route(struct lightningd_state *dstate, const struct pubkey *to, - u64 msatoshi, s64 *fee, + u64 msatoshi, + double riskfactor, + s64 *fee, struct node_connection ***route); struct node_map *empty_node_map(struct lightningd_state *dstate); diff --git a/daemon/test/test.sh b/daemon/test/test.sh index eb51970c2..b5f71dbdf 100755 --- a/daemon/test/test.sh +++ b/daemon/test/test.sh @@ -1013,7 +1013,7 @@ if [ ! -n "$MANUALCOMMIT" ]; then RHASH5=`lcli3 accept-payment $HTLC_AMOUNT | sed 's/.*"\([0-9a-f]*\)".*/\1/'` # Get route. - ROUTE=`lcli1 getroute $ID3 $HTLC_AMOUNT` + ROUTE=`lcli1 getroute $ID3 $HTLC_AMOUNT 1` ROUTE=`echo $ROUTE | sed 's/^{ "route" : \(.*\) }$/\1/'` # Try wrong hash. @@ -1058,7 +1058,7 @@ if [ ! -n "$MANUALCOMMIT" ]; then fi # Can't pay twice (try from node2) - ROUTE2=`lcli2 getroute $ID3 $HTLC_AMOUNT` + ROUTE2=`lcli2 getroute $ID3 $HTLC_AMOUNT 1` ROUTE2=`echo $ROUTE2 | sed 's/^{ "route" : \(.*\) }$/\1/'` if lcli2 sendpay "$ROUTE2" $RHASH5; then echo "Paying twice worked?" >&2 @@ -1083,7 +1083,7 @@ if [ ! -n "$MANUALCOMMIT" ]; then fi # Now node1 should fail to route (route deleted) - if lcli1 getroute $ID3 $HTLC_AMOUNT | $FGREP "no route found"; then : ; + if lcli1 getroute $ID3 $HTLC_AMOUNT 1 | $FGREP "no route found"; then : ; else echo "Pay to node3 didn't fail instantly second time" >&2 exit 1