mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-03-03 10:46:58 +01:00
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:
parent
e2bc70bfce
commit
ca80fc0286
6 changed files with 60 additions and 12 deletions
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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);
|
||||||
|
|
13
daemon/pay.c
13
daemon/pay.c
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Reference in a new issue