getroute: add a risk factor.

We need some way to reflect the tradeoff between the possible delay if
a payment gets stuck, and the fees charged by nodes.  This adds a risk
factor which reflects the probability that a node goes down, and the
cost associated with losing access to our funds for a given time.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell 2016-09-06 16:47:48 +09:30
parent e2bc70bfce
commit ca80fc0286
6 changed files with 60 additions and 12 deletions

View file

@ -64,6 +64,16 @@ bool json_tok_u64(const char *buffer, const jsmntok_t *tok,
return true; 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, bool json_tok_number(const char *buffer, const jsmntok_t *tok,
unsigned int *num) unsigned int *num)
{ {

View file

@ -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, bool json_tok_u64(const char *buffer, const jsmntok_t *tok,
uint64_t *num); 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) */ /* 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, bool json_tok_bitcoin_amount(const char *buffer, const jsmntok_t *tok,
uint64_t *satoshi); uint64_t *satoshi);

View file

@ -199,11 +199,12 @@ static void json_getroute(struct command *cmd,
const char *buffer, const jsmntok_t *params) const char *buffer, const jsmntok_t *params)
{ {
struct pubkey id; struct pubkey id;
jsmntok_t *idtok, *msatoshitok; jsmntok_t *idtok, *msatoshitok, *riskfactortok;
struct json_result *response; struct json_result *response;
int i; int i;
u64 msatoshi; u64 msatoshi;
s64 fee; s64 fee;
double riskfactor;
struct node_connection **route; struct node_connection **route;
struct peer *peer; struct peer *peer;
u64 *amounts, total_amount; u64 *amounts, total_amount;
@ -212,6 +213,7 @@ static void json_getroute(struct command *cmd,
if (!json_get_params(buffer, params, if (!json_get_params(buffer, params,
"id", &idtok, "id", &idtok,
"msatoshi", &msatoshitok, "msatoshi", &msatoshitok,
"riskfactor", &riskfactortok,
NULL)) { NULL)) {
command_fail(cmd, "Need id and msatoshi"); command_fail(cmd, "Need id and msatoshi");
return; return;
@ -231,7 +233,14 @@ static void json_getroute(struct command *cmd,
return; 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) { if (!peer) {
command_fail(cmd, "no route found"); command_fail(cmd, "no route found");
return; return;

View file

@ -11,6 +11,9 @@
#include <ccan/structeq/structeq.h> #include <ccan/structeq/structeq.h>
#include <inttypes.h> #include <inttypes.h>
/* 365.25 * 24 * 60 / 10 */
#define BLOCKS_PER_YEAR 52596
static const secp256k1_pubkey *keyof_node(const struct node *n) static const secp256k1_pubkey *keyof_node(const struct node *n)
{ {
return &n->id.pubkey; 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)) { for (n = node_map_first(nodes, &it); n; n = node_map_next(nodes, &it)) {
size_t i; 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].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; 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 /* We track totals, rather than costs. That's because the fee depends
* on the current amount passing through. */ * 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]; struct node_connection *c = node->in[edgenum];
size_t h; size_t h;
assert(c->dst == node); assert(c->dst == node);
for (h = 0; h < ROUTING_MAX_HOPS; h++) { for (h = 0; h < ROUTING_MAX_HOPS; h++) {
/* FIXME: Bias towards smaller expiry values. */
/* FIXME: Bias against smaller channels. */ /* FIXME: Bias against smaller channels. */
s64 fee = connection_fee(c, node->bfg[h].total); 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].total = node->bfg[h].total + fee;
c->src->bfg[h+1].risk = risk;
c->src->bfg[h+1].prev = c; 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, struct peer *find_route(struct lightningd_state *dstate,
const struct pubkey *to, const struct pubkey *to,
u64 msatoshi, s64 *fee, u64 msatoshi,
double riskfactor,
s64 *fee,
struct node_connection ***route) struct node_connection ***route)
{ {
struct node *n, *src, *dst; 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 /* Bellman-Ford-Gibson: like Bellman-Ford, but keep values for
* every path length. */ * every path length. */
src->bfg[0].total = msatoshi; src->bfg[0].total = msatoshi;
src->bfg[0].risk = 0;
for (runs = 0; runs < ROUTING_MAX_HOPS; runs++) { for (runs = 0; runs < ROUTING_MAX_HOPS; runs++) {
log_debug(dstate->base_log, "Run %i", 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)) { n = node_map_next(dstate->nodes, &it)) {
size_t num_edges = tal_count(n->in); size_t num_edges = tal_count(n->in);
for (i = 0; i < num_edges; i++) { 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", log_debug(dstate->base_log, "We seek %p->%p, this is %p -> %p",
dst, src, n->in[i]->src, n->in[i]->dst); dst, src, n->in[i]->src, n->in[i]->dst);
log_debug_struct(dstate->base_log, log_debug_struct(dstate->base_log,

View file

@ -27,6 +27,8 @@ struct node {
struct { struct {
/* Total to get to here from target. */ /* Total to get to here from target. */
s64 total; s64 total;
/* Total risk premium of this route. */
u64 risk;
/* Where that came from. */ /* Where that came from. */
struct node_connection *prev; struct node_connection *prev;
} bfg[ROUTING_MAX_HOPS+1]; } bfg[ROUTING_MAX_HOPS+1];
@ -56,7 +58,9 @@ void remove_connection(struct lightningd_state *dstate,
struct peer *find_route(struct lightningd_state *dstate, struct peer *find_route(struct lightningd_state *dstate,
const struct pubkey *to, const struct pubkey *to,
u64 msatoshi, s64 *fee, u64 msatoshi,
double riskfactor,
s64 *fee,
struct node_connection ***route); struct node_connection ***route);
struct node_map *empty_node_map(struct lightningd_state *dstate); struct node_map *empty_node_map(struct lightningd_state *dstate);

View file

@ -1013,7 +1013,7 @@ if [ ! -n "$MANUALCOMMIT" ]; then
RHASH5=`lcli3 accept-payment $HTLC_AMOUNT | sed 's/.*"\([0-9a-f]*\)".*/\1/'` RHASH5=`lcli3 accept-payment $HTLC_AMOUNT | sed 's/.*"\([0-9a-f]*\)".*/\1/'`
# Get route. # Get route.
ROUTE=`lcli1 getroute $ID3 $HTLC_AMOUNT` ROUTE=`lcli1 getroute $ID3 $HTLC_AMOUNT 1`
ROUTE=`echo $ROUTE | sed 's/^{ "route" : \(.*\) }$/\1/'` ROUTE=`echo $ROUTE | sed 's/^{ "route" : \(.*\) }$/\1/'`
# Try wrong hash. # Try wrong hash.
@ -1058,7 +1058,7 @@ if [ ! -n "$MANUALCOMMIT" ]; then
fi fi
# Can't pay twice (try from node2) # 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/'` ROUTE2=`echo $ROUTE2 | sed 's/^{ "route" : \(.*\) }$/\1/'`
if lcli2 sendpay "$ROUTE2" $RHASH5; then if lcli2 sendpay "$ROUTE2" $RHASH5; then
echo "Paying twice worked?" >&2 echo "Paying twice worked?" >&2
@ -1083,7 +1083,7 @@ if [ ! -n "$MANUALCOMMIT" ]; then
fi fi
# Now node1 should fail to route (route deleted) # 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 else
echo "Pay to node3 didn't fail instantly second time" >&2 echo "Pay to node3 didn't fail instantly second time" >&2
exit 1 exit 1